source: inundation/pyvolution/quantity.py @ 1752

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

Refactored quantity.set_values

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