source: inundation/pyvolution/quantity.py @ 2492

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

Changed domain.get_quantity() to return a quantity object rather than the values.
Updated tests accordingly

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