source: inundation/pyvolution/quantity.py @ 1750

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

Work towards new set_quantity API

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