source: inundation/pyvolution/quantity.py @ 2380

Last change on this file since 2380 was 2366, checked in by ole, 19 years ago

Cleanup

File size: 44.2 KB
Line 
1"""Class Quantity - Implements values at each triangular element
2
3To create:
4
5   Quantity(domain, vertex_values)
6
7   domain: Associated domain structure. Required.
8
9   vertex_values: N x 3 array of values at each vertex for each element.
10                  Default None
11
12   If vertex_values are None Create array of zeros compatible with domain.
13   Otherwise check that it is compatible with dimenions of domain.
14   Otherwise raise an exception
15"""
16
17
18class Quantity:
19
20    def __init__(self, domain, vertex_values=None):
21
22        from mesh import Mesh
23        from Numeric import array, zeros, Float
24
25        msg = 'First argument in Quantity.__init__ '
26        msg += 'must be of class Mesh (or a subclass thereof)'
27        assert isinstance(domain, Mesh), msg
28
29        if vertex_values is None:
30            N = domain.number_of_elements
31            self.vertex_values = zeros((N, 3), Float)
32        else:
33            self.vertex_values = array(vertex_values).astype(Float)
34
35            N, V = self.vertex_values.shape
36            assert V == 3,\
37                   'Three vertex values per element must be specified'
38
39
40            msg = 'Number of vertex values (%d) must be consistent with'\
41                  %N
42            msg += 'number of elements in specified domain (%d).'\
43                   %domain.number_of_elements
44
45            assert N == domain.number_of_elements, msg
46
47        self.domain = domain
48
49        #Allocate space for other quantities
50        self.centroid_values = zeros(N, Float)
51        self.edge_values = zeros((N, 3), Float)
52
53        #Intialise centroid and edge_values
54        self.interpolate()
55
56
57
58    #Methods for operator overloading
59    def __len__(self):
60        return self.centroid_values.shape[0]
61
62
63    def __neg__(self):
64        """Negate all values in this quantity giving meaning to the
65        expression -Q where Q is an instance of class Quantity
66        """
67
68        Q = Quantity(self.domain)
69        Q.set_values(-self.vertex_values)
70        return Q
71
72
73    def __add__(self, other):
74        """Add to self anything that could populate a quantity
75
76        E.g other can be a constant, an array, a function, another quantity
77        (except for a filename or points, attributes (for now))
78        - see set_values for details
79        """
80
81        Q = Quantity(self.domain)
82        Q.set_values(other)
83
84        result = Quantity(self.domain)
85        result.set_values(self.vertex_values + Q.vertex_values)
86        return result
87
88    def __radd__(self, other):
89        """Handle cases like 7+Q, where Q is an instance of class Quantity
90        """
91        return self + other
92
93
94    def __sub__(self, other):
95        return self + -other  #Invoke __neg__
96
97    def __mul__(self, other):
98        """Multiply self with anything that could populate a quantity
99
100        E.g other can be a constant, an array, a function, another quantity
101        (except for a filename or points, attributes (for now))
102        - see set_values for details
103
104        Note that if two quantitites q1 and q2 are multiplied,
105        vertex values are multiplied entry by entry
106        while centroid and edge values are re-interpolated.
107        Hence they won't be the product of centroid or edge values
108        from q1 and q2.
109        """
110
111        Q = Quantity(self.domain)
112        Q.set_values(other)
113
114        result = Quantity(self.domain)
115        result.set_values(self.vertex_values * Q.vertex_values)
116        return result
117
118    def __rmul__(self, other):
119        """Handle cases like 3*Q, where Q is an instance of class Quantity
120        """
121        return self * other
122
123    def __pow__(self, other):
124        """Raise quantity to (numerical) power
125
126        As with __mul__ vertex values are processed entry by entry
127        while centroid and edge values are re-interpolated.
128
129        Example using __pow__:
130          Q = (Q1**2 + Q2**2)**0.5
131
132        """
133
134        result = Quantity(self.domain)
135        result.set_values(self.vertex_values**other)
136        return result
137
138
139
140    def interpolate(self):
141        """Compute interpolated values at edges and centroid
142        Pre-condition: vertex_values have been set
143        """
144
145        N = self.vertex_values.shape[0]
146        for i in range(N):
147            v0 = self.vertex_values[i, 0]
148            v1 = self.vertex_values[i, 1]
149            v2 = self.vertex_values[i, 2]
150
151            self.centroid_values[i] = (v0 + v1 + v2)/3
152
153        self.interpolate_from_vertices_to_edges()
154
155
156    def interpolate_from_vertices_to_edges(self):
157        #Call correct module function
158        #(either from this module or C-extension)
159        interpolate_from_vertices_to_edges(self)
160
161
162
163
164    #New leaner interface to setting values
165    def set_values(self,
166                   numeric = None,    # List, numeric array or constant
167                   quantity = None,   # Another quantity
168                   function = None,   # Callable object: f(x,y)
169                   geospatial_data = None, #Arbitrary dataset
170                   points = None, values = None, data_georef = None, #Input for least squares (obsoleted by use of geo_spatial object)
171                   filename = None, attribute_name = None, #Input from file
172                   alpha = None,
173                   location = 'vertices',
174                   indices = None,
175                   verbose = False,
176                   use_cache = False):
177
178        """Set values for quantity based on different sources.
179
180        numeric:
181          Compatible list, Numeric array (see below) or constant.
182          If callable it will treated as a function (see below)
183          If instance of another Quantity it will be treated as such.
184          If geo_spatial object it will be treated as such
185
186        quantity:
187          Another quantity (compatible quantity, e.g. obtained as a
188          linear combination of quantities)
189
190        function:
191          Any callable object that takes two 1d arrays x and y
192          each of length N and returns an array also of length N.
193          The function will be evaluated at points determined by
194          location and indices in the underlying mesh.
195
196        geospatial_data:
197          Arbitrary geo spatial dataset in the form of the class
198          Geospatial_data. Mesh points are populated using least squares
199          fitting           
200
201        points:
202          Nx2 array of data points for use with least squares fit
203          If points are present, an N array of attribute
204          values corresponding to
205          each data point must be present.
206
207        values:
208          If points is specified, values is an array of length N containing
209          attribute values for each point.
210
211        data_georef:
212          If points is specified, geo_reference applies to each point.       
213         
214        filename:
215          Name of a .pts file containing data points and attributes for
216          use with least squares.
217         
218        attribute_name:
219          If specified, any array matching that name
220          will be used. from file or geospatial_data.
221          Otherwise a default will be used.
222
223        alpha:
224          Smoothing parameter to be used with least squares fits.
225          See module least_squares for further details about alpha.
226          Alpha will only be used with points, values or filename.
227          Otherwise it will be ignored.
228
229
230        location: Where values are to be stored.
231                  Permissible options are: vertices, edges, centroids
232                  Default is 'vertices'
233
234                  In case of location == 'centroids' the dimension values must
235                  be a list of a Numerical array of length N,
236                  N being the number of elements.
237                  Otherwise it must be of dimension Nx3
238
239
240                  The values will be stored in elements following their
241                  internal ordering.
242
243                  If location is not 'unique vertices' Indices is the
244                  set of element ids that the operation applies to.
245                  If location is 'unique vertices' Indices is the set
246                  of vertex ids that the operation applies to.
247
248                  If selected location is vertices, values for
249                  centroid and edges will be assigned interpolated
250                  values.  In any other case, only values for the
251                  specified locations will be assigned and the others
252                  will be left undefined.
253
254        verbose: True means that output to stdout is generated
255
256        use_cache: True means that caching of intermediate results is
257                   attempted for least squares fit.
258
259
260
261
262        Exactly one of the arguments
263          numeric, quantity, function, points, filename
264        must be present.
265        """
266
267        from geospatial_data.geospatial_data import Geospatial_data
268        from types import FloatType, IntType, LongType, ListType, NoneType
269        from Numeric import ArrayType
270
271        #General input checks
272        L = [numeric, quantity, function, geospatial_data, points, filename]
273        msg = 'Exactly one of the arguments '+\
274              'numeric, quantity, function, geospatial_data, points, or filename '+\
275              'must be present.'
276        assert L.count(None) == len(L)-1, msg
277
278
279        if location not in ['vertices', 'centroids', 'edges',
280                            'unique vertices']:
281            msg = 'Invalid location: %s' %location
282            raise msg
283
284
285        msg = 'Indices must be a list or None'
286        assert type(indices) in [ListType, NoneType, ArrayType], msg
287
288
289
290        #Determine which 'set_values_from_...' to use
291
292        if numeric is not None:
293            if type(numeric) in [FloatType, IntType, LongType]:
294                self.set_values_from_constant(numeric,
295                                              location, indices, verbose)
296            elif type(numeric) in [ArrayType, ListType]:
297                self.set_values_from_array(numeric,
298                                           location, indices, verbose)
299            elif callable(numeric):
300                self.set_values_from_function(numeric,
301                                              location, indices, verbose)
302            elif isinstance(numeric, Quantity):
303                self.set_values_from_quantity(numeric,
304                                              location, indices, verbose)
305            elif isinstance(numeric, Geospatial_data):
306                self.set_values_from_geospatial_data(numeric,               
307                                                     alpha,
308                                                     location, indices,
309                                                     verbose = verbose,
310                                                     use_cache = use_cache)
311            else:
312                msg = 'Illegal type for argument numeric: %s' %str(numeric)
313                raise msg
314
315        elif quantity is not None:
316            self.set_values_from_quantity(quantity,
317                                          location, indices, verbose)
318        elif function is not None:
319            msg = 'Argument function must be callable'
320            assert callable(function), msg
321            self.set_values_from_function(function,
322                                          location, indices, verbose)
323        elif geospatial_data is not None:
324                self.set_values_from_geospatial_data(geospatial_data,
325                                                     alpha,
326                                                     location, indices,
327                                                     verbose = verbose,
328                                                     use_cache = use_cache)
329        elif points is not None:
330            print 'The usage of points in set_values will be deprecated.' +\
331                  'Please use the geospatial_data object.'
332           
333            msg = 'When points are specified, associated values must also be.'
334            assert values is not None, msg
335            self.set_values_from_points(points, values, alpha,
336                                        location, indices,
337                                        data_georef = data_georef,
338                                        verbose = verbose,
339                                        use_cache = use_cache)
340        elif filename is not None:
341            self.set_values_from_file(filename, attribute_name, alpha,
342                                      location, indices,
343                                      verbose = verbose,
344                                      use_cache = use_cache) 
345        else:
346            raise 'This can\'t happen :-)'
347
348
349        #Update all locations in triangles
350        if location == 'vertices' or location == 'unique vertices':
351            #Intialise centroid and edge_values
352            self.interpolate()
353
354        if location == 'centroids':
355            #Extrapolate 1st order - to capture notion of area being specified
356            self.extrapolate_first_order()
357
358
359
360    #Specific functions for setting values
361    def set_values_from_constant(self, X,
362                                 location, indices, verbose):
363        """Set quantity values from specified constant X
364        """
365
366
367        if location == 'centroids':
368            if (indices ==  None):
369                self.centroid_values[:] = X
370            else:
371                #Brute force
372                for i in indices:
373                    self.centroid_values[i,:] = X
374
375        elif location == 'edges':
376            if (indices ==  None):
377                self.edge_values[:] = X
378            else:
379                #Brute force
380                for i in indices:
381                    self.edge_values[i,:] = X
382
383        elif location == 'unique vertices':
384            if (indices ==  None):
385                self.edge_values[:] = X
386            else:
387
388                #Go through list of unique vertices
389                for unique_vert_id in indices:
390                    triangles = self.domain.vertexlist[unique_vert_id]
391
392                    #In case there are unused points
393                    if triangles is None: continue
394
395                    #Go through all triangle, vertex pairs
396                    #and set corresponding vertex value
397                    for triangle_id, vertex_id in triangles:
398                        self.vertex_values[triangle_id, vertex_id] = X
399
400                    #Intialise centroid and edge_values
401                    self.interpolate()
402        else:
403            if (indices ==  None):
404                self.vertex_values[:] = X
405            else:
406                #Brute force
407                for i_vertex in indices:
408                    self.vertex_values[i_vertex,:] = X
409
410
411
412
413
414
415    def set_values_from_array(self, values,
416                              location, indices, verbose):
417        """Set values for quantity
418
419        values: Numeric array
420        location: Where values are to be stored.
421        Permissible options are: vertices, edges, centroid, unique vertices
422        Default is 'vertices'
423
424        indices - if this action is carried out on a subset of
425        elements or unique vertices
426        The element/unique vertex indices are specified here.
427
428        In case of location == 'centroid' the dimension values must
429        be a list of a Numerical array of length N, N being the number
430        of elements.
431
432        Otherwise it must be of dimension Nx3
433
434        The values will be stored in elements following their
435        internal ordering.
436
437        If selected location is vertices, values for centroid and edges
438        will be assigned interpolated values.
439        In any other case, only values for the specified locations
440        will be assigned and the others will be left undefined.
441        """
442
443        from Numeric import array, Float, Int, allclose
444
445        values = array(values).astype(Float)
446
447        if indices is not None:
448            indices = array(indices).astype(Int)
449            msg = 'Number of values must match number of indices'
450            assert values.shape[0] == indices.shape[0], msg
451
452        N = self.centroid_values.shape[0]
453
454        if location == 'centroids':
455            assert len(values.shape) == 1, 'Values array must be 1d'
456
457            if indices is None:
458                msg = 'Number of values must match number of elements'
459                assert values.shape[0] == N, msg
460
461                self.centroid_values = values
462            else:
463                msg = 'Number of values must match number of indices'
464                assert values.shape[0] == indices.shape[0], msg
465
466                #Brute force
467                for i in range(len(indices)):
468                    self.centroid_values[indices[i]] = values[i]
469
470        elif location == 'edges':
471            assert len(values.shape) == 2, 'Values array must be 2d'
472
473            msg = 'Number of values must match number of elements'
474            assert values.shape[0] == N, msg
475
476            msg = 'Array must be N x 3'
477            assert values.shape[1] == 3, msg
478
479            self.edge_values = values
480
481        elif location == 'unique vertices':
482            assert len(values.shape) == 1 or allclose(values.shape[1:], 1),\
483                   'Values array must be 1d'
484
485            self.set_vertex_values(values.flat, indices = indices)
486        else:
487            if len(values.shape) == 1:
488                self.set_vertex_values(values, indices = indices)
489                #if indices == None:
490                    #Values are being specified once for each unique vertex
491                #    msg = 'Number of values must match number of vertices'
492                #    assert values.shape[0] == self.domain.coordinates.shape[0], msg
493                 #   self.set_vertex_values(values)
494                #else:
495                #    for element_index, value in map(None, indices, values):
496                #        self.vertex_values[element_index, :] = value
497
498            elif len(values.shape) == 2:
499                #Vertex values are given as a triplet for each triangle
500
501                msg = 'Array must be N x 3'
502                assert values.shape[1] == 3, msg
503
504                if indices == None:
505                    self.vertex_values = values
506                else:
507                    for element_index, value in map(None, indices, values):
508                        self.vertex_values[element_index] = value
509            else:
510                msg = 'Values array must be 1d or 2d'
511                raise msg
512
513    def set_values_from_quantity(self, q,
514                                 location, indices, verbose):
515        """Set quantity values from specified quantity instance q
516
517        Location is ignored
518        """
519
520
521        A = q.vertex_values
522
523        from Numeric import allclose
524        msg = 'Quantities are defined on different meshes. '+\
525              'This might be a case for implementing interpolation '+\
526              'between different meshes.'
527        assert allclose(A.shape, self.vertex_values.shape), msg
528
529        self.set_values(A, location='vertices',
530                        indices=indices,
531                        verbose=verbose)
532
533
534    def set_values_from_function(self, f,
535                                 location, indices, verbose):
536        """Set values for quantity using specified function
537
538        f: x, y -> z Function where x, y and z are arrays
539        location: Where values are to be stored.
540                  Permissible options are: vertices, centroid, edges,
541                  unique vertices
542                  Default is "vertices"
543        """
544
545        #FIXME: Should check that function returns something sensible and
546        #raise a meaningfull exception if it returns None for example
547
548        #FIXME: Should supply absolute coordinates
549
550        from Numeric import take
551
552        if (indices is None):
553            indices = range(len(self))
554            is_subset = False
555        else:
556            is_subset = True
557
558        if location == 'centroids':
559            P = take(self.domain.centroid_coordinates, indices)
560            if is_subset:
561                self.set_values(f(P[:,0], P[:,1]),
562                                location = location,
563                                indices = indices)
564            else:
565                self.set_values(f(P[:,0], P[:,1]), location = location)
566        elif location == 'vertices':
567            P = self.domain.vertex_coordinates
568            if is_subset:
569                #Brute force
570                for e in indices:
571                    for i in range(3):
572                        self.vertex_values[e,i] = f(P[e,2*i], P[e,2*i+1])
573            else:
574                for i in range(3):
575                    self.vertex_values[:,i] = f(P[:,2*i], P[:,2*i+1])
576        else:
577            raise 'Not implemented: %s' %location
578
579
580       
581    def set_values_from_geospatial_data(self, geospatial_data, alpha,
582                                        location, indices,
583                                        verbose = False,
584                                        use_cache = False):
585
586        #FIXME: Use this function for the time being. Later move code in here
587
588        points = geospatial_data.get_data_points()
589        values = geospatial_data.get_attributes()
590        data_georef = geospatial_data.get_geo_reference()
591
592
593
594        self.set_values_from_points(points, values, alpha,
595                                    location, indices,
596                                    data_georef = data_georef,
597                                    verbose = verbose,
598                                    use_cache = use_cache)                                   
599
600                               
601
602    def set_values_from_points(self, points, values, alpha,
603                               location, indices,
604                               data_georef = None,
605                               verbose = False,
606                               use_cache = False):
607        """Set quantity values from arbitray data points using least squares
608        """
609
610
611        from Numeric import Float
612        from util import ensure_numeric
613        from least_squares import fit_to_mesh
614        from coordinate_transforms.geo_reference import Geo_reference
615       
616
617        points = ensure_numeric(points, Float)
618        values = ensure_numeric(values, Float)
619
620        if location != 'vertices':
621            msg = 'set_values_from_points is only defined for '+\
622                  'location=\'vertices\''
623            raise msg
624
625        coordinates = self.domain.coordinates
626        triangles = self.domain.triangles
627
628
629        #Take care of georeferencing
630        if data_georef is None:
631            data_georef = Geo_reference()
632
633
634        mesh_georef = self.domain.geo_reference
635
636        #print mesh_georef
637        #print data_georef
638        #print points
639           
640
641        #Call least squares method           
642        args = (coordinates, triangles, points, values)
643        kwargs = {'data_origin': data_georef.get_origin(),
644                  'mesh_origin': mesh_georef.get_origin(),
645                  'alpha': alpha,
646                  'precrop': True,
647                  'verbose': verbose}
648
649        #print kwargs
650       
651        if use_cache is True:
652            try:
653                from caching import cache
654            except:
655                msg = 'Caching was requested, but caching module'+\
656                      'could not be imported'
657                raise msg
658
659            vertex_attributes = cache(fit_to_mesh,
660                                      args, kwargs,
661                                      verbose = verbose)
662        else:
663
664            vertex_attributes = apply(fit_to_mesh,
665                                      args, kwargs)
666           
667        #Call underlying method using array values   
668        self.set_values_from_array(vertex_attributes,
669                                   location, indices, verbose)
670
671
672
673    def set_values_from_file(self, filename, attribute_name, alpha,
674                             location, indices,
675                             verbose = False,
676                             use_cache = False):
677        """Set quantity based on arbitrary points in .pts file
678        using least_squares attribute_name selects name of attribute
679        present in file.
680        If not specified try to use whatever is available in file.
681        """
682
683        from load_mesh.loadASCII import import_points_file
684        from geospatial_data.geospatial_data import points_dictionary2geospatial_data
685
686        from types import StringType
687        msg = 'Filename must be a text string'
688        assert type(filename) == StringType, msg
689
690
691        #Read from (NetCDF) file
692        points_dict = import_points_file(filename)
693        points = points_dict['pointlist']
694        attributes = points_dict['attributelist']
695
696        if attribute_name is None:
697            names = attributes.keys()
698            attribute_name = names[0]
699
700        msg = 'Attribute_name must be a text string'
701        assert type(attribute_name) == StringType, msg
702
703
704        if verbose:
705            print 'Using attribute %s from file %s' %(attribute_name, filename)
706            print 'Available attributes: %s' %(names)
707
708        #try:
709        #    z = attributes[attribute_name]
710        #except:
711        #    msg = 'Could not extract attribute %s from file %s'\
712        #          %(attribute_name, filename)
713        #    raise msg
714
715
716        #Take care of georeferencing
717        if points_dict.has_key('geo_reference') and \
718               points_dict['geo_reference'] is not None:
719            data_georef = points_dict['geo_reference']
720        else:
721            data_georef = None
722
723       
724
725        #Call underlying method for geospatial data
726        geospatial_data = points_dictionary2geospatial_data(points_dict)
727        geospatial_data.set_default_attribute_name(attribute_name)
728           
729        self.set_values_from_geospatial_data(geospatial_data,
730                                             alpha,
731                                             location, indices,
732                                             verbose = verbose,
733                                             use_cache = use_cache)
734       
735        #Call underlying method for points
736        #self.set_values_from_points(points, z, alpha,
737        #                            location, indices,
738        #                            data_georef = data_georef,
739        #                            verbose = verbose,
740        #                            use_cache = use_cache)
741
742
743
744    def get_values(self, location='vertices', indices = None):
745        """get values for quantity
746
747        return X, Compatible list, Numeric array (see below)
748        location: Where values are to be stored.
749                  Permissible options are: vertices, edges, centroid
750                  and unique vertices. Default is 'vertices'
751
752        In case of location == 'centroids' the dimension values must
753        be a list of a Numerical array of length N, N being the number
754        of elements. Otherwise it must be of dimension Nx3
755
756        The returned values with be a list the length of indices
757        (N if indices = None).  Each value will be a list of the three
758        vertex values for this quantity.
759
760        Indices is the set of element ids that the operation applies to.
761
762        """
763        from Numeric import take
764
765        if location not in ['vertices', 'centroids', 'edges', 'unique vertices']:
766            msg = 'Invalid location: %s' %location
767            raise msg
768
769        import types, Numeric
770        assert type(indices) in [types.ListType, types.NoneType,
771                                 Numeric.ArrayType],\
772                                 'Indices must be a list or None'
773
774        if location == 'centroids':
775            if (indices ==  None):
776                indices = range(len(self))
777            return take(self.centroid_values,indices)
778        elif location == 'edges':
779            if (indices ==  None):
780                indices = range(len(self))
781            return take(self.edge_values,indices)
782        elif location == 'unique vertices':
783            if (indices ==  None):
784                indices=range(self.domain.coordinates.shape[0])
785            vert_values = []
786            #Go through list of unique vertices
787            for unique_vert_id in indices:
788                triangles = self.domain.vertexlist[unique_vert_id]
789
790                #In case there are unused points
791                if triangles is None:
792                    msg = 'Unique vertex not associated with triangles'
793                    raise msg
794
795                # Go through all triangle, vertex pairs
796                # Average the values
797                sum = 0
798                for triangle_id, vertex_id in triangles:
799                    sum += self.vertex_values[triangle_id, vertex_id]
800                vert_values.append(sum/len(triangles))
801            return Numeric.array(vert_values)
802        else:
803            if (indices ==  None):
804                indices = range(len(self))
805            return take(self.vertex_values,indices)
806
807
808
809    def set_vertex_values(self, A, indices = None):
810        """Set vertex values for all unique vertices based on input array A
811        which has one entry per unique vertex, i.e.
812        one value for each row in array self.domain.coordinates or
813        one value for each row in vertexlist.
814
815        indices is the list of vertex_id's that will be set.
816
817        This function is used by set_values_from_array
818        """
819
820        from Numeric import array, Float
821
822        #Assert that A can be converted to a Numeric array of appropriate dim
823        A = array(A, Float)
824
825        #print 'SHAPE A', A.shape
826        assert len(A.shape) == 1
827
828        if indices == None:
829            assert A.shape[0] == self.domain.coordinates.shape[0]
830            vertex_list = range(A.shape[0])
831        else:
832            assert A.shape[0] == len(indices)
833            vertex_list = indices
834
835        #Go through list of unique vertices
836        for i_index, unique_vert_id in enumerate(vertex_list):
837            triangles = self.domain.vertexlist[unique_vert_id]
838
839            if triangles is None: continue #In case there are unused points
840
841            #Go through all triangle, vertex pairs
842            #touching vertex unique_vert_id and set corresponding vertex value
843            for triangle_id, vertex_id in triangles:
844                self.vertex_values[triangle_id, vertex_id] = A[i_index]
845
846        #Intialise centroid and edge_values
847        self.interpolate()
848
849
850    def smooth_vertex_values(self, value_array='field_values',
851                             precision = None):
852        """ Smooths field_values or conserved_quantities data.
853        TODO: be able to smooth individual fields
854        NOTE:  This function does not have a test.
855        FIXME: NOT DONE - do we need it?
856        FIXME: this function isn't called by anything.
857               Maybe it should be removed..-DSG
858        """
859
860        from Numeric import concatenate, zeros, Float, Int, array, reshape
861
862
863        A,V = self.get_vertex_values(xy=False,
864                                     value_array=value_array,
865                                     smooth = True,
866                                     precision = precision)
867
868        #Set some field values
869        for volume in self:
870            for i,v in enumerate(volume.vertices):
871                if value_array == 'field_values':
872                    volume.set_field_values('vertex', i, A[v,:])
873                elif value_array == 'conserved_quantities':
874                    volume.set_conserved_quantities('vertex', i, A[v,:])
875
876        if value_array == 'field_values':
877            self.precompute()
878        elif value_array == 'conserved_quantities':
879            Volume.interpolate_conserved_quantities()
880
881
882    #Method for outputting model results
883    #FIXME: Split up into geometric and numeric stuff.
884    #FIXME: Geometric (X,Y,V) should live in mesh.py
885    #FIXME: STill remember to move XY to mesh
886    def get_vertex_values(self,
887                          xy=True,
888                          smooth = None,
889                          precision = None,
890                          reduction = None):
891        """Return vertex values like an OBJ format
892
893        The vertex values are returned as one sequence in the 1D float array A.
894        If requested the coordinates will be returned in 1D arrays X and Y.
895
896        The connectivity is represented as an integer array, V, of dimension
897        M x 3, where M is the number of volumes. Each row has three indices
898        into the X, Y, A arrays defining the triangle.
899
900        if smooth is True, vertex values corresponding to one common
901        coordinate set will be smoothed according to the given
902        reduction operator. In this case vertex coordinates will be
903        de-duplicated.
904
905        If no smoothings is required, vertex coordinates and values will
906        be aggregated as a concatenation of values at
907        vertices 0, vertices 1 and vertices 2
908
909
910        Calling convention
911        if xy is True:
912           X,Y,A,V = get_vertex_values
913        else:
914           A,V = get_vertex_values
915
916        """
917
918        from Numeric import concatenate, zeros, Float, Int, array, reshape
919
920
921        if smooth is None:
922            smooth = self.domain.smooth
923
924        if precision is None:
925            precision = Float
926
927        if reduction is None:
928            reduction = self.domain.reduction
929
930        #Create connectivity
931
932        if smooth == True:
933
934            V = self.domain.get_vertices()
935            N = len(self.domain.vertexlist)
936            A = zeros(N, precision)
937
938            #Smoothing loop
939            for k in range(N):
940                L = self.domain.vertexlist[k]
941
942                #Go through all triangle, vertex pairs
943                #contributing to vertex k and register vertex value
944
945                if L is None: continue #In case there are unused points
946
947                contributions = []
948                for volume_id, vertex_id in L:
949                    v = self.vertex_values[volume_id, vertex_id]
950                    contributions.append(v)
951
952                A[k] = reduction(contributions)
953
954
955            if xy is True:
956                X = self.domain.coordinates[:,0].astype(precision)
957                Y = self.domain.coordinates[:,1].astype(precision)
958
959                return X, Y, A, V
960            else:
961                return A, V
962        else:
963            #Don't smooth
964            #obj machinery moved to general_mesh
965
966            # Create a V like [[0 1 2], [3 4 5]....[3*m-2 3*m-1 3*m]]
967            # These vert_id's will relate to the verts created below
968            #m = len(self.domain)  #Number of volumes
969            #M = 3*m        #Total number of unique vertices
970            #V = reshape(array(range(M)).astype(Int), (m,3))
971
972            V = self.domain.get_triangles(obj=True)
973            #FIXME use get_vertices, when ready
974
975            A = self.vertex_values.flat
976
977            #Do vertex coordinates
978            if xy is True:
979                C = self.domain.get_vertex_coordinates()
980
981                X = C[:,0:6:2].copy()
982                Y = C[:,1:6:2].copy()
983
984                return X.flat, Y.flat, A, V
985            else:
986                return A, V
987
988
989    def extrapolate_first_order(self):
990        """Extrapolate conserved quantities from centroid to
991        vertices for each volume using
992        first order scheme.
993        """
994
995        qc = self.centroid_values
996        qv = self.vertex_values
997
998        for i in range(3):
999            qv[:,i] = qc
1000
1001
1002    def get_integral(self):
1003        """Compute the integral of quantity across entire domain
1004        """
1005        integral = 0
1006        for k in range(self.domain.number_of_elements):
1007            area = self.domain.areas[k]
1008            qc = self.centroid_values[k]
1009            integral += qc*area
1010
1011        return integral
1012
1013
1014
1015
1016class Conserved_quantity(Quantity):
1017    """Class conserved quantity adds to Quantity:
1018
1019    boundary values, storage and method for updating, and
1020    methods for (second order) extrapolation from centroid to vertices inluding
1021    gradients and limiters
1022    """
1023
1024    def __init__(self, domain, vertex_values=None):
1025        Quantity.__init__(self, domain, vertex_values)
1026
1027        from Numeric import zeros, Float
1028
1029        #Allocate space for boundary values
1030        L = len(domain.boundary)
1031        self.boundary_values = zeros(L, Float)
1032
1033        #Allocate space for updates of conserved quantities by
1034        #flux calculations and forcing functions
1035
1036        N = domain.number_of_elements
1037        self.explicit_update = zeros(N, Float )
1038        self.semi_implicit_update = zeros(N, Float )
1039
1040
1041    def update(self, timestep):
1042        #Call correct module function
1043        #(either from this module or C-extension)
1044        return update(self, timestep)
1045
1046
1047    def compute_gradients(self):
1048        #Call correct module function
1049        #(either from this module or C-extension)
1050        return compute_gradients(self)
1051
1052
1053    def limit(self):
1054        #Call correct module function
1055        #(either from this module or C-extension)
1056        limit(self)
1057
1058
1059    def extrapolate_second_order(self):
1060        #Call correct module function
1061        #(either from this module or C-extension)
1062        extrapolate_second_order(self)
1063
1064
1065def update(quantity, timestep):
1066    """Update centroid values based on values stored in
1067    explicit_update and semi_implicit_update as well as given timestep
1068
1069    Function implementing forcing terms must take on argument
1070    which is the domain and they must update either explicit
1071    or implicit updates, e,g,:
1072
1073    def gravity(domain):
1074        ....
1075        domain.quantities['xmomentum'].explicit_update = ...
1076        domain.quantities['ymomentum'].explicit_update = ...
1077
1078
1079
1080    Explicit terms must have the form
1081
1082        G(q, t)
1083
1084    and explicit scheme is
1085
1086       q^{(n+1}) = q^{(n)} + delta_t G(q^{n}, n delta_t)
1087
1088
1089    Semi implicit forcing terms are assumed to have the form
1090
1091       G(q, t) = H(q, t) q
1092
1093    and the semi implicit scheme will then be
1094
1095      q^{(n+1}) = q^{(n)} + delta_t H(q^{n}, n delta_t) q^{(n+1})
1096
1097
1098    """
1099
1100    from Numeric import sum, equal, ones, Float
1101
1102    N = quantity.centroid_values.shape[0]
1103
1104
1105    #Divide H by conserved quantity to obtain G (see docstring above)
1106
1107
1108    for k in range(N):
1109        x = quantity.centroid_values[k]
1110        if x == 0.0:
1111            #FIXME: Is this right
1112            quantity.semi_implicit_update[k] = 0.0
1113        else:
1114            quantity.semi_implicit_update[k] /= x
1115
1116    #Explicit updates
1117    quantity.centroid_values += timestep*quantity.explicit_update
1118
1119    #Semi implicit updates
1120    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
1121
1122    if sum(equal(denominator, 0.0)) > 0.0:
1123        msg = 'Zero division in semi implicit update. Call Stephen :-)'
1124        raise msg
1125    else:
1126        #Update conserved_quantities from semi implicit updates
1127        quantity.centroid_values /= denominator
1128
1129
1130def interpolate_from_vertices_to_edges(quantity):
1131    """Compute edge values from vertex values using linear interpolation
1132    """
1133
1134    for k in range(quantity.vertex_values.shape[0]):
1135        q0 = quantity.vertex_values[k, 0]
1136        q1 = quantity.vertex_values[k, 1]
1137        q2 = quantity.vertex_values[k, 2]
1138
1139        quantity.edge_values[k, 0] = 0.5*(q1+q2)
1140        quantity.edge_values[k, 1] = 0.5*(q0+q2)
1141        quantity.edge_values[k, 2] = 0.5*(q0+q1)
1142
1143
1144
1145def extrapolate_second_order(quantity):
1146    """Extrapolate conserved quantities from centroid to
1147    vertices for each volume using
1148    second order scheme.
1149    """
1150
1151    a, b = quantity.compute_gradients()
1152
1153    X = quantity.domain.get_vertex_coordinates()
1154    qc = quantity.centroid_values
1155    qv = quantity.vertex_values
1156
1157    #Check each triangle
1158    for k in range(quantity.domain.number_of_elements):
1159        #Centroid coordinates
1160        x, y = quantity.domain.centroid_coordinates[k]
1161
1162        #vertex coordinates
1163        x0, y0, x1, y1, x2, y2 = X[k,:]
1164
1165        #Extrapolate
1166        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
1167        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
1168        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)
1169
1170
1171def compute_gradients(quantity):
1172    """Compute gradients of triangle surfaces defined by centroids of
1173    neighbouring volumes.
1174    If one edge is on the boundary, use own centroid as neighbour centroid.
1175    If two or more are on the boundary, fall back to first order scheme.
1176    """
1177
1178    from Numeric import zeros, Float
1179    from util import gradient
1180
1181    centroid_coordinates = quantity.domain.centroid_coordinates
1182    surrogate_neighbours = quantity.domain.surrogate_neighbours
1183    centroid_values = quantity.centroid_values
1184    number_of_boundaries = quantity.domain.number_of_boundaries
1185
1186    N = centroid_values.shape[0]
1187
1188    a = zeros(N, Float)
1189    b = zeros(N, Float)
1190
1191    for k in range(N):
1192        if number_of_boundaries[k] < 2:
1193            #Two or three true neighbours
1194
1195            #Get indices of neighbours (or self when used as surrogate)
1196            k0, k1, k2 = surrogate_neighbours[k,:]
1197
1198            #Get data
1199            q0 = centroid_values[k0]
1200            q1 = centroid_values[k1]
1201            q2 = centroid_values[k2]
1202
1203            x0, y0 = centroid_coordinates[k0] #V0 centroid
1204            x1, y1 = centroid_coordinates[k1] #V1 centroid
1205            x2, y2 = centroid_coordinates[k2] #V2 centroid
1206
1207            #Gradient
1208            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
1209
1210        elif number_of_boundaries[k] == 2:
1211            #One true neighbour
1212
1213            #Get index of the one neighbour
1214            for k0 in surrogate_neighbours[k,:]:
1215                if k0 != k: break
1216            assert k0 != k
1217
1218            k1 = k  #self
1219
1220            #Get data
1221            q0 = centroid_values[k0]
1222            q1 = centroid_values[k1]
1223
1224            x0, y0 = centroid_coordinates[k0] #V0 centroid
1225            x1, y1 = centroid_coordinates[k1] #V1 centroid
1226
1227            #Gradient
1228            a[k], b[k] = gradient2(x0, y0, x1, y1, q0, q1)
1229        else:
1230            #No true neighbours -
1231            #Fall back to first order scheme
1232            pass
1233
1234
1235    return a, b
1236
1237
1238
1239def limit(quantity):
1240    """Limit slopes for each volume to eliminate artificial variance
1241    introduced by e.g. second order extrapolator
1242
1243    This is an unsophisticated limiter as it does not take into
1244    account dependencies among quantities.
1245
1246    precondition:
1247    vertex values are estimated from gradient
1248    postcondition:
1249    vertex values are updated
1250    """
1251
1252    from Numeric import zeros, Float
1253
1254    N = quantity.domain.number_of_elements
1255
1256    beta_w = quantity.domain.beta_w
1257
1258    qc = quantity.centroid_values
1259    qv = quantity.vertex_values
1260
1261    #Find min and max of this and neighbour's centroid values
1262    qmax = zeros(qc.shape, Float)
1263    qmin = zeros(qc.shape, Float)
1264
1265    for k in range(N):
1266        qmax[k] = qmin[k] = qc[k]
1267        for i in range(3):
1268            n = quantity.domain.neighbours[k,i]
1269            if n >= 0:
1270                qn = qc[n] #Neighbour's centroid value
1271
1272                qmin[k] = min(qmin[k], qn)
1273                qmax[k] = max(qmax[k], qn)
1274
1275
1276    #Diffences between centroids and maxima/minima
1277    dqmax = qmax - qc
1278    dqmin = qmin - qc
1279
1280    #Deltas between vertex and centroid values
1281    dq = zeros(qv.shape, Float)
1282    for i in range(3):
1283        dq[:,i] = qv[:,i] - qc
1284
1285    #Phi limiter
1286    for k in range(N):
1287
1288        #Find the gradient limiter (phi) across vertices
1289        phi = 1.0
1290        for i in range(3):
1291            r = 1.0
1292            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
1293            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
1294
1295            phi = min( min(r*beta_w, 1), phi )
1296
1297        #Then update using phi limiter
1298        for i in range(3):
1299            qv[k,i] = qc[k] + phi*dq[k,i]
1300
1301
1302
1303from utilities import compile
1304if compile.can_use_C_extension('quantity_ext.c'):
1305    #Replace python version with c implementations
1306
1307    from quantity_ext import limit, compute_gradients,\
1308    extrapolate_second_order, interpolate_from_vertices_to_edges, update
Note: See TracBrowser for help on using the repository browser.