source: inundation/pyvolution/quantity.py @ 1884

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

Implemented operator overloading for quantities
Implemented set_values with quantity as input
+ testing

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