source: inundation/pyvolution/quantity.py @ 2309

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

Deployed geo_spatial stuff

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 = None,
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        self.set_values_from_points(points, values, alpha,
594                                    location, indices,
595                                    data_georef = data_georef,
596                                    verbose = verbose,
597                                    use_cache = use_cache)                                   
598
599                               
600
601    def set_values_from_points(self, points, values, alpha,
602                               location, indices,
603                               data_georef = None,
604                               verbose = False,
605                               use_cache = False):
606        """Set quantity values from arbitray data points using least squares
607        """
608
609        from Numeric import Float
610        from util import ensure_numeric
611        from least_squares import fit_to_mesh
612        from coordinate_transforms.geo_reference import Geo_reference
613       
614
615        points = ensure_numeric(points, Float)
616        values = ensure_numeric(values, Float)
617
618        if location != 'vertices':
619            msg = 'set_values_from_points is only defined for '+\
620                  'location=\'vertices\''
621            raise msg
622
623        coordinates = self.domain.coordinates
624        triangles = self.domain.triangles
625
626
627        #Take care of georeferencing
628        if data_georef is None:
629            data_georef = Geo_reference()
630
631
632        mesh_georef = self.domain.geo_reference
633
634        #print mesh_georef
635        #print data_georef
636        #print points
637           
638
639        #Call least squares method           
640        args = (coordinates, triangles, points, values)
641        kwargs = {'data_origin': data_georef.get_origin(),
642                  'mesh_origin': mesh_georef.get_origin(),
643                  'alpha': alpha,
644                  'verbose': verbose}
645
646        #print kwargs
647       
648        if use_cache is True:
649            try:
650                from caching import cache
651            except:
652                msg = 'Caching was requested, but caching module'+\
653                      'could not be imported'
654                raise msg
655
656            vertex_attributes = cache(fit_to_mesh,
657                                      args, kwargs,
658                                      verbose = verbose)
659        else:
660
661            vertex_attributes = apply(fit_to_mesh,
662                                      args, kwargs)
663           
664        #Call underlying method using array values   
665        self.set_values_from_array(vertex_attributes,
666                                   location, indices, verbose)
667
668
669
670    def set_values_from_file(self, filename, attribute_name, alpha,
671                             location, indices,
672                             verbose = False,
673                             use_cache = False):
674        """Set quantity based on arbitrary points in .pts file
675        using least_squares attribute_name selects name of attribute
676        present in file.
677        If not specified try to use whatever is available in file.
678        """
679
680        from load_mesh.loadASCII import import_points_file
681        from geospatial_data.geospatial_data import points_dictionary2geospatial_data
682
683        from types import StringType
684        msg = 'Filename must be a text string'
685        assert type(filename) == StringType, msg
686
687
688        #Read from (NetCDF) file
689        points_dict = import_points_file(filename)
690        points = points_dict['pointlist']
691        attributes = points_dict['attributelist']
692
693        if attribute_name is None:
694            names = attributes.keys()
695            attribute_name = names[0]
696
697        msg = 'Attribute_name must be a text string'
698        assert type(attribute_name) == StringType, msg
699
700
701        if verbose:
702            print 'Using attribute %s from file %s' %(attribute_name, filename)
703            print 'Available attributes: %s' %(names)
704
705        #try:
706        #    z = attributes[attribute_name]
707        #except:
708        #    msg = 'Could not extract attribute %s from file %s'\
709        #          %(attribute_name, filename)
710        #    raise msg
711
712
713        #Take care of georeferencing
714        if points_dict.has_key('geo_reference') and \
715               points_dict['geo_reference'] is not None:
716            data_georef = points_dict['geo_reference']
717        else:
718            data_georef = None
719
720
721
722        #Call underlying method for geospatial data
723        geospatial_data = points_dictionary2geospatial_data(points_dict)
724        geospatial_data.set_default_attribute_name(attribute_name)
725           
726        self.set_values_from_geospatial_data(geospatial_data,
727                                             alpha,
728                                             location, indices,
729                                             verbose = verbose,
730                                             use_cache = use_cache)
731       
732        #Call underlying method for points
733        #self.set_values_from_points(points, z, alpha,
734        #                            location, indices,
735        #                            data_georef = data_georef,
736        #                            verbose = verbose,
737        #                            use_cache = use_cache)
738
739
740
741    def get_values(self, location='vertices', indices = None):
742        """get values for quantity
743
744        return X, Compatible list, Numeric array (see below)
745        location: Where values are to be stored.
746                  Permissible options are: vertices, edges, centroid
747                  and unique vertices. Default is 'vertices'
748
749        In case of location == 'centroids' the dimension values must
750        be a list of a Numerical array of length N, N being the number
751        of elements. Otherwise it must be of dimension Nx3
752
753        The returned values with be a list the length of indices
754        (N if indices = None).  Each value will be a list of the three
755        vertex values for this quantity.
756
757        Indices is the set of element ids that the operation applies to.
758
759        """
760        from Numeric import take
761
762        if location not in ['vertices', 'centroids', 'edges', 'unique vertices']:
763            msg = 'Invalid location: %s' %location
764            raise msg
765
766        import types, Numeric
767        assert type(indices) in [types.ListType, types.NoneType,
768                                 Numeric.ArrayType],\
769                                 'Indices must be a list or None'
770
771        if location == 'centroids':
772            if (indices ==  None):
773                indices = range(len(self))
774            return take(self.centroid_values,indices)
775        elif location == 'edges':
776            if (indices ==  None):
777                indices = range(len(self))
778            return take(self.edge_values,indices)
779        elif location == 'unique vertices':
780            if (indices ==  None):
781                indices=range(self.domain.coordinates.shape[0])
782            vert_values = []
783            #Go through list of unique vertices
784            for unique_vert_id in indices:
785                triangles = self.domain.vertexlist[unique_vert_id]
786
787                #In case there are unused points
788                if triangles is None:
789                    msg = 'Unique vertex not associated with triangles'
790                    raise msg
791
792                # Go through all triangle, vertex pairs
793                # Average the values
794                sum = 0
795                for triangle_id, vertex_id in triangles:
796                    sum += self.vertex_values[triangle_id, vertex_id]
797                vert_values.append(sum/len(triangles))
798            return Numeric.array(vert_values)
799        else:
800            if (indices ==  None):
801                indices = range(len(self))
802            return take(self.vertex_values,indices)
803
804
805
806    def set_vertex_values(self, A, indices = None):
807        """Set vertex values for all unique vertices based on input array A
808        which has one entry per unique vertex, i.e.
809        one value for each row in array self.domain.coordinates or
810        one value for each row in vertexlist.
811
812        indices is the list of vertex_id's that will be set.
813
814        This function is used by set_values_from_array
815        """
816
817        from Numeric import array, Float
818
819        #Assert that A can be converted to a Numeric array of appropriate dim
820        A = array(A, Float)
821
822        #print 'SHAPE A', A.shape
823        assert len(A.shape) == 1
824
825        if indices == None:
826            assert A.shape[0] == self.domain.coordinates.shape[0]
827            vertex_list = range(A.shape[0])
828        else:
829            assert A.shape[0] == len(indices)
830            vertex_list = indices
831
832        #Go through list of unique vertices
833        for i_index, unique_vert_id in enumerate(vertex_list):
834            triangles = self.domain.vertexlist[unique_vert_id]
835
836            if triangles is None: continue #In case there are unused points
837
838            #Go through all triangle, vertex pairs
839            #touching vertex unique_vert_id and set corresponding vertex value
840            for triangle_id, vertex_id in triangles:
841                self.vertex_values[triangle_id, vertex_id] = A[i_index]
842
843        #Intialise centroid and edge_values
844        self.interpolate()
845
846
847    def smooth_vertex_values(self, value_array='field_values',
848                             precision = None):
849        """ Smooths field_values or conserved_quantities data.
850        TODO: be able to smooth individual fields
851        NOTE:  This function does not have a test.
852        FIXME: NOT DONE - do we need it?
853        FIXME: this function isn't called by anything.
854               Maybe it should be removed..-DSG
855        """
856
857        from Numeric import concatenate, zeros, Float, Int, array, reshape
858
859
860        A,V = self.get_vertex_values(xy=False,
861                                     value_array=value_array,
862                                     smooth = True,
863                                     precision = precision)
864
865        #Set some field values
866        for volume in self:
867            for i,v in enumerate(volume.vertices):
868                if value_array == 'field_values':
869                    volume.set_field_values('vertex', i, A[v,:])
870                elif value_array == 'conserved_quantities':
871                    volume.set_conserved_quantities('vertex', i, A[v,:])
872
873        if value_array == 'field_values':
874            self.precompute()
875        elif value_array == 'conserved_quantities':
876            Volume.interpolate_conserved_quantities()
877
878
879    #Method for outputting model results
880    #FIXME: Split up into geometric and numeric stuff.
881    #FIXME: Geometric (X,Y,V) should live in mesh.py
882    #FIXME: STill remember to move XY to mesh
883    def get_vertex_values(self,
884                          xy=True,
885                          smooth = None,
886                          precision = None,
887                          reduction = None):
888        """Return vertex values like an OBJ format
889
890        The vertex values are returned as one sequence in the 1D float array A.
891        If requested the coordinates will be returned in 1D arrays X and Y.
892
893        The connectivity is represented as an integer array, V, of dimension
894        M x 3, where M is the number of volumes. Each row has three indices
895        into the X, Y, A arrays defining the triangle.
896
897        if smooth is True, vertex values corresponding to one common
898        coordinate set will be smoothed according to the given
899        reduction operator. In this case vertex coordinates will be
900        de-duplicated.
901
902        If no smoothings is required, vertex coordinates and values will
903        be aggregated as a concatenation of values at
904        vertices 0, vertices 1 and vertices 2
905
906
907        Calling convention
908        if xy is True:
909           X,Y,A,V = get_vertex_values
910        else:
911           A,V = get_vertex_values
912
913        """
914
915        from Numeric import concatenate, zeros, Float, Int, array, reshape
916
917
918        if smooth is None:
919            smooth = self.domain.smooth
920
921        if precision is None:
922            precision = Float
923
924        if reduction is None:
925            reduction = self.domain.reduction
926
927        #Create connectivity
928
929        if smooth == True:
930
931            V = self.domain.get_vertices()
932            N = len(self.domain.vertexlist)
933            A = zeros(N, precision)
934
935            #Smoothing loop
936            for k in range(N):
937                L = self.domain.vertexlist[k]
938
939                #Go through all triangle, vertex pairs
940                #contributing to vertex k and register vertex value
941
942                if L is None: continue #In case there are unused points
943
944                contributions = []
945                for volume_id, vertex_id in L:
946                    v = self.vertex_values[volume_id, vertex_id]
947                    contributions.append(v)
948
949                A[k] = reduction(contributions)
950
951
952            if xy is True:
953                X = self.domain.coordinates[:,0].astype(precision)
954                Y = self.domain.coordinates[:,1].astype(precision)
955
956                return X, Y, A, V
957            else:
958                return A, V
959        else:
960            #Don't smooth
961            #obj machinery moved to general_mesh
962
963            # Create a V like [[0 1 2], [3 4 5]....[3*m-2 3*m-1 3*m]]
964            # These vert_id's will relate to the verts created below
965            #m = len(self.domain)  #Number of volumes
966            #M = 3*m        #Total number of unique vertices
967            #V = reshape(array(range(M)).astype(Int), (m,3))
968
969            V = self.domain.get_triangles(obj=True)
970            #FIXME use get_vertices, when ready
971
972            A = self.vertex_values.flat
973
974            #Do vertex coordinates
975            if xy is True:
976                C = self.domain.get_vertex_coordinates()
977
978                X = C[:,0:6:2].copy()
979                Y = C[:,1:6:2].copy()
980
981                return X.flat, Y.flat, A, V
982            else:
983                return A, V
984
985
986    def extrapolate_first_order(self):
987        """Extrapolate conserved quantities from centroid to
988        vertices for each volume using
989        first order scheme.
990        """
991
992        qc = self.centroid_values
993        qv = self.vertex_values
994
995        for i in range(3):
996            qv[:,i] = qc
997
998
999    def get_integral(self):
1000        """Compute the integral of quantity across entire domain
1001        """
1002        integral = 0
1003        for k in range(self.domain.number_of_elements):
1004            area = self.domain.areas[k]
1005            qc = self.centroid_values[k]
1006            integral += qc*area
1007
1008        return integral
1009
1010
1011
1012
1013class Conserved_quantity(Quantity):
1014    """Class conserved quantity adds to Quantity:
1015
1016    boundary values, storage and method for updating, and
1017    methods for (second order) extrapolation from centroid to vertices inluding
1018    gradients and limiters
1019    """
1020
1021    def __init__(self, domain, vertex_values=None):
1022        Quantity.__init__(self, domain, vertex_values)
1023
1024        from Numeric import zeros, Float
1025
1026        #Allocate space for boundary values
1027        L = len(domain.boundary)
1028        self.boundary_values = zeros(L, Float)
1029
1030        #Allocate space for updates of conserved quantities by
1031        #flux calculations and forcing functions
1032
1033        N = domain.number_of_elements
1034        self.explicit_update = zeros(N, Float )
1035        self.semi_implicit_update = zeros(N, Float )
1036
1037
1038    def update(self, timestep):
1039        #Call correct module function
1040        #(either from this module or C-extension)
1041        return update(self, timestep)
1042
1043
1044    def compute_gradients(self):
1045        #Call correct module function
1046        #(either from this module or C-extension)
1047        return compute_gradients(self)
1048
1049
1050    def limit(self):
1051        #Call correct module function
1052        #(either from this module or C-extension)
1053        limit(self)
1054
1055
1056    def extrapolate_second_order(self):
1057        #Call correct module function
1058        #(either from this module or C-extension)
1059        extrapolate_second_order(self)
1060
1061
1062def update(quantity, timestep):
1063    """Update centroid values based on values stored in
1064    explicit_update and semi_implicit_update as well as given timestep
1065
1066    Function implementing forcing terms must take on argument
1067    which is the domain and they must update either explicit
1068    or implicit updates, e,g,:
1069
1070    def gravity(domain):
1071        ....
1072        domain.quantities['xmomentum'].explicit_update = ...
1073        domain.quantities['ymomentum'].explicit_update = ...
1074
1075
1076
1077    Explicit terms must have the form
1078
1079        G(q, t)
1080
1081    and explicit scheme is
1082
1083       q^{(n+1}) = q^{(n)} + delta_t G(q^{n}, n delta_t)
1084
1085
1086    Semi implicit forcing terms are assumed to have the form
1087
1088       G(q, t) = H(q, t) q
1089
1090    and the semi implicit scheme will then be
1091
1092      q^{(n+1}) = q^{(n)} + delta_t H(q^{n}, n delta_t) q^{(n+1})
1093
1094
1095    """
1096
1097    from Numeric import sum, equal, ones, Float
1098
1099    N = quantity.centroid_values.shape[0]
1100
1101
1102    #Divide H by conserved quantity to obtain G (see docstring above)
1103
1104
1105    for k in range(N):
1106        x = quantity.centroid_values[k]
1107        if x == 0.0:
1108            #FIXME: Is this right
1109            quantity.semi_implicit_update[k] = 0.0
1110        else:
1111            quantity.semi_implicit_update[k] /= x
1112
1113    #Explicit updates
1114    quantity.centroid_values += timestep*quantity.explicit_update
1115
1116    #Semi implicit updates
1117    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
1118
1119    if sum(equal(denominator, 0.0)) > 0.0:
1120        msg = 'Zero division in semi implicit update. Call Stephen :-)'
1121        raise msg
1122    else:
1123        #Update conserved_quantities from semi implicit updates
1124        quantity.centroid_values /= denominator
1125
1126
1127def interpolate_from_vertices_to_edges(quantity):
1128    """Compute edge values from vertex values using linear interpolation
1129    """
1130
1131    for k in range(quantity.vertex_values.shape[0]):
1132        q0 = quantity.vertex_values[k, 0]
1133        q1 = quantity.vertex_values[k, 1]
1134        q2 = quantity.vertex_values[k, 2]
1135
1136        quantity.edge_values[k, 0] = 0.5*(q1+q2)
1137        quantity.edge_values[k, 1] = 0.5*(q0+q2)
1138        quantity.edge_values[k, 2] = 0.5*(q0+q1)
1139
1140
1141
1142def extrapolate_second_order(quantity):
1143    """Extrapolate conserved quantities from centroid to
1144    vertices for each volume using
1145    second order scheme.
1146    """
1147
1148    a, b = quantity.compute_gradients()
1149
1150    X = quantity.domain.get_vertex_coordinates()
1151    qc = quantity.centroid_values
1152    qv = quantity.vertex_values
1153
1154    #Check each triangle
1155    for k in range(quantity.domain.number_of_elements):
1156        #Centroid coordinates
1157        x, y = quantity.domain.centroid_coordinates[k]
1158
1159        #vertex coordinates
1160        x0, y0, x1, y1, x2, y2 = X[k,:]
1161
1162        #Extrapolate
1163        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
1164        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
1165        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)
1166
1167
1168def compute_gradients(quantity):
1169    """Compute gradients of triangle surfaces defined by centroids of
1170    neighbouring volumes.
1171    If one edge is on the boundary, use own centroid as neighbour centroid.
1172    If two or more are on the boundary, fall back to first order scheme.
1173    """
1174
1175    from Numeric import zeros, Float
1176    from util import gradient
1177
1178    centroid_coordinates = quantity.domain.centroid_coordinates
1179    surrogate_neighbours = quantity.domain.surrogate_neighbours
1180    centroid_values = quantity.centroid_values
1181    number_of_boundaries = quantity.domain.number_of_boundaries
1182
1183    N = centroid_values.shape[0]
1184
1185    a = zeros(N, Float)
1186    b = zeros(N, Float)
1187
1188    for k in range(N):
1189        if number_of_boundaries[k] < 2:
1190            #Two or three true neighbours
1191
1192            #Get indices of neighbours (or self when used as surrogate)
1193            k0, k1, k2 = surrogate_neighbours[k,:]
1194
1195            #Get data
1196            q0 = centroid_values[k0]
1197            q1 = centroid_values[k1]
1198            q2 = centroid_values[k2]
1199
1200            x0, y0 = centroid_coordinates[k0] #V0 centroid
1201            x1, y1 = centroid_coordinates[k1] #V1 centroid
1202            x2, y2 = centroid_coordinates[k2] #V2 centroid
1203
1204            #Gradient
1205            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
1206
1207        elif number_of_boundaries[k] == 2:
1208            #One true neighbour
1209
1210            #Get index of the one neighbour
1211            for k0 in surrogate_neighbours[k,:]:
1212                if k0 != k: break
1213            assert k0 != k
1214
1215            k1 = k  #self
1216
1217            #Get data
1218            q0 = centroid_values[k0]
1219            q1 = centroid_values[k1]
1220
1221            x0, y0 = centroid_coordinates[k0] #V0 centroid
1222            x1, y1 = centroid_coordinates[k1] #V1 centroid
1223
1224            #Gradient
1225            a[k], b[k] = gradient2(x0, y0, x1, y1, q0, q1)
1226        else:
1227            #No true neighbours -
1228            #Fall back to first order scheme
1229            pass
1230
1231
1232    return a, b
1233
1234
1235
1236def limit(quantity):
1237    """Limit slopes for each volume to eliminate artificial variance
1238    introduced by e.g. second order extrapolator
1239
1240    This is an unsophisticated limiter as it does not take into
1241    account dependencies among quantities.
1242
1243    precondition:
1244    vertex values are estimated from gradient
1245    postcondition:
1246    vertex values are updated
1247    """
1248
1249    from Numeric import zeros, Float
1250
1251    N = quantity.domain.number_of_elements
1252
1253    beta_w = quantity.domain.beta_w
1254
1255    qc = quantity.centroid_values
1256    qv = quantity.vertex_values
1257
1258    #Find min and max of this and neighbour's centroid values
1259    qmax = zeros(qc.shape, Float)
1260    qmin = zeros(qc.shape, Float)
1261
1262    for k in range(N):
1263        qmax[k] = qmin[k] = qc[k]
1264        for i in range(3):
1265            n = quantity.domain.neighbours[k,i]
1266            if n >= 0:
1267                qn = qc[n] #Neighbour's centroid value
1268
1269                qmin[k] = min(qmin[k], qn)
1270                qmax[k] = max(qmax[k], qn)
1271
1272
1273    #Diffences between centroids and maxima/minima
1274    dqmax = qmax - qc
1275    dqmin = qmin - qc
1276
1277    #Deltas between vertex and centroid values
1278    dq = zeros(qv.shape, Float)
1279    for i in range(3):
1280        dq[:,i] = qv[:,i] - qc
1281
1282    #Phi limiter
1283    for k in range(N):
1284
1285        #Find the gradient limiter (phi) across vertices
1286        phi = 1.0
1287        for i in range(3):
1288            r = 1.0
1289            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
1290            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
1291
1292            phi = min( min(r*beta_w, 1), phi )
1293
1294        #Then update using phi limiter
1295        for i in range(3):
1296            qv[k,i] = qc[k] + phi*dq[k,i]
1297
1298
1299
1300from utilities import compile
1301if compile.can_use_C_extension('quantity_ext.c'):
1302    #Replace python version with c implementations
1303
1304    from quantity_ext import limit, compute_gradients,\
1305    extrapolate_second_order, interpolate_from_vertices_to_edges, update
Note: See TracBrowser for help on using the repository browser.