source: inundation/pyvolution/quantity.py @ 2212

Last change on this file since 2212 was 2173, checked in by sexton, 19 years ago

Improved doc string

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