source: inundation/ga/storm_surge/pyvolution/quantity.py @ 659

Last change on this file since 659 was 659, checked in by ole, 20 years ago

Added functionality for settimng quantities according to polygons

File size: 25.8 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    def set_values(self, X, location='vertices', indexes = None):
82        """Set values for quantity
83
84        X: Compatible list, Numeric array (see below), constant or function
85        location: Where values are to be stored.
86                  Permissible options are: vertices, edges, centroid
87                  Default is "vertices"
88
89        In case of location == 'centroid' the dimension values must
90        be a list of a Numerical array of length N, N being the number
91        of elements. Otherwise it must be of dimension Nx3
92
93        The values will be stored in elements following their
94        internal ordering.
95
96        If values are described a function, it will be evaluated at specified points
97        Indexes is the set of element ids that the operation applies to.
98       
99        If selected location is vertices, values for centroid and edges
100        will be assigned interpolated values.
101        In any other case, only values for the specified locations
102        will be assigned and the others will be left undefined.
103        """
104
105        if location not in ['vertices', 'centroids', 'edges']:
106            msg = 'Invalid location: %s' %location
107            raise msg
108
109        if X is None:
110            msg = 'Given values are None'
111            raise msg           
112       
113        import types
114       
115        if callable(X):
116            #Use function specific method
117            self.set_function_values(X, location, indexes = indexes)       
118        elif type(X) in [types.FloatType, types.IntType, types.LongType]:
119            if location == 'centroids':
120                if (indexes ==  None):
121                    self.centroid_values[:] = X
122                else:
123                    #Brute force
124                    for i in indexes:
125                        self.centroid_values[i,:] = X
126                       
127            elif location == 'edges':
128                if (indexes ==  None):
129                    self.edge_values[:] = X
130                else:
131                    #Brute force
132                    for i in indexes:
133                        self.edge_values[i,:] = X
134            else:
135                if (indexes ==  None):
136                    self.vertex_values[:] = X
137                else:
138                    #Brute force
139                    for i_vertex in indexes:
140                        self.vertex_values[i_vertex,:] = X     
141
142        else:
143            #Use array specific method
144            self.set_array_values(X, location, indexes = indexes)
145
146        if location == 'vertices':
147            #Intialise centroid and edge_values
148            self.interpolate()
149           
150        if location == 'centroids':
151            #Extrapolate 1st order - to capture notion of area being specified
152            self.extrapolate_first_order()           
153         
154    def get_values(self, location='vertices', indexes = None):
155        """get values for quantity
156
157        return X, Compatible list, Numeric array (see below)
158        location: Where values are to be stored.
159                  Permissible options are: vertices, edges, centroid
160                  Default is "vertices"
161
162        In case of location == 'centroid' the dimension values must
163        be a list of a Numerical array of length N, N being the number
164        of elements. Otherwise it must be of dimension Nx3
165
166        The returned values with be a list the length of indexes
167        (N if indexes = None).  Each value will be a list of the three
168        vertex values for this quantity.
169       
170        Indexes is the set of element ids that the operation applies to.
171       
172        """
173        from Numeric import take
174
175        if location not in ['vertices', 'centroids', 'edges']:
176            msg = 'Invalid location: %s' %location
177            raise msg         
178       
179        if (indexes ==  None):
180            indexes = range(len(self))
181           
182        if location == 'centroids':
183            return take(self.centroid_values,indexes)                       
184        elif location == 'edges':
185            return take(self.edge_values,indexes)
186        else:
187            return take(self.vertex_values,indexes)
188
189
190    def set_function_values(self, f, location='vertices', indexes = None):
191        """Set values for quantity using specified function
192
193        f: x, y -> z Function where x, y and z are arrays
194        location: Where values are to be stored.
195                  Permissible options are: vertices, centroid
196                  Default is "vertices"
197        """
198        from Numeric import take
199
200        if (indexes ==  None):
201            indexes = range(len(self))
202            is_subset = False
203        else:
204            is_subset = True
205        if location == 'centroids':
206            P = take(self.domain.centroid_coordinates,indexes)
207            if is_subset:
208                self.set_values(f(P[:,0], P[:,1]), location, indexes = indexes)
209            else:
210                self.set_values(f(P[:,0], P[:,1]), location)
211        elif location == 'vertices':
212            P = self.domain.vertex_coordinates
213            if is_subset:
214                #Brute force
215                for e in indexes:
216                    for i in range(3):
217                        self.vertex_values[e,i] = f(P[e,2*i], P[e,2*i+1])
218            else:
219                for i in range(3):
220                    self.vertex_values[:,i] = f(P[:,2*i], P[:,2*i+1])
221        else:
222            raise 'Not implemented: %s' %location
223
224           
225    def set_array_values(self, values, location='vertices', indexes = None):
226        """Set values for quantity
227
228        values: Numeric array
229        location: Where values are to be stored.
230                  Permissible options are: vertices, edges, centroid
231                  Default is "vertices"
232       
233        indexes - if this action is carried out on a subset of elements
234        The element indexes are specified here.
235       
236        In case of location == 'centroid' the dimension values must
237        be a list of a Numerical array of length N, N being the number
238        of elements.
239
240        Otherwise it must be of dimension Nx3
241
242        The values will be stored in elements following their
243        internal ordering.
244
245        If selected location is vertices, values for centroid and edges
246        will be assigned interpolated values.
247        In any other case, only values for the specified locations
248        will be assigned and the others will be left undefined.
249        """
250
251        from Numeric import array, Float, Int
252
253        values = array(values).astype(Float)
254
255        if (indexes <> None):
256            indexes = array(indexes).astype(Int)
257            msg = 'Number of values must match number of indexes'
258            assert values.shape[0] == indexes.shape[0], msg
259           
260        N = self.centroid_values.shape[0]
261       
262        if location == 'centroids':
263            assert len(values.shape) == 1, 'Values array must be 1d'
264
265            if indexes == None:
266                msg = 'Number of values must match number of elements'
267                assert values.shape[0] == N, msg
268           
269                self.centroid_values = values
270            else:
271                msg = 'Number of values must match number of indexes'
272                assert values.shape[0] == indexes.shape[0], msg
273               
274                #Brute force
275                for i in range(len(indexes)):
276                    self.centroid_values[indexes[i]] = values[i]
277                   
278        elif location == 'edges':
279            assert len(values.shape) == 2, 'Values array must be 2d'
280
281            msg = 'Number of values must match number of elements'
282            assert values.shape[0] == N, msg
283           
284            msg = 'Array must be N x 3'           
285            assert values.shape[1] == 3, msg
286           
287            self.edge_values = values
288        else:
289            if len(values.shape) == 1:
290
291                if indexes == None:
292                    #Values are being specified once for each unique vertex
293                    msg = 'Number of values must match number of vertices'
294                    assert values.shape[0] == self.domain.coordinates.shape[0], msg
295                    self.set_vertex_values(values)
296                else:
297                    for element_index, value in map(None, indexes, values): 
298                        self.vertex_values[element_index, :] = value
299                       
300            elif len(values.shape) == 2:
301                #Vertex values are given as a triplet for each triangle
302               
303                msg = 'Array must be N x 3'           
304                assert values.shape[1] == 3, msg
305               
306                if indexes == None:
307                    self.vertex_values = values
308                else:
309                    for element_index, value in map(None, indexes, values): 
310                        self.vertex_values[element_index] = value
311            else:   
312                msg = 'Values array must be 1d or 2d'
313                raise msg
314           
315
316    # FIXME have a get_vertex_values as well, so the 'level' quantity can be
317    # set, based on the elevation   
318    def set_vertex_values(self, A):
319        """Set vertex values for all triangles based on input array A
320        which is assumed to have one entry per (unique) vertex, i.e.
321        one value for each row in array self.domain.coordinates.
322        """
323
324        from Numeric import array, Float
325
326        #Assert that A can be converted to a Numeric array of appropriate dim
327        A = array(A, Float)
328
329        assert len(A.shape) == 1
330        assert A.shape[0] == self.domain.coordinates.shape[0]
331
332        N = A.shape[0]
333       
334        #Go through list of unique vertices
335        for k in range(N):
336            L = self.domain.vertexlist[k]
337               
338            if L is None: continue #In case there are unused points
339
340            #Go through all triangle, vertex pairs
341            #touching vertex k and set corresponding vertex value
342            for triangle_id, vertex_id in L:
343                self.vertex_values[triangle_id, vertex_id] = A[k]
344               
345        #Intialise centroid and edge_values
346        self.interpolate()
347
348 
349    def smooth_vertex_values(self, value_array='field_values',
350                             precision = None):
351        """ Smooths field_values or conserved_quantities data.
352        TODO: be able to smooth individual fields
353        NOTE:  This function does not have a test.
354        FIXME: NOT DONE - do we need it?
355        FIXME: this function isn't called by anything.
356               Maybe it should be removed..-DSG
357        """
358
359        from Numeric import concatenate, zeros, Float, Int, array, reshape
360
361       
362        A,V = self.get_vertex_values(xy=False,
363                                     value_array=value_array,
364                                     smooth = True,
365                                     precision = precision)
366
367        #Set some field values
368        for volume in self:
369            for i,v in enumerate(volume.vertices):
370                if value_array == 'field_values':
371                    volume.set_field_values('vertex', i, A[v,:])
372                elif value_array == 'conserved_quantities':
373                    volume.set_conserved_quantities('vertex', i, A[v,:])
374
375        if value_array == 'field_values':
376            self.precompute()
377        elif value_array == 'conserved_quantities':
378            Volume.interpolate_conserved_quantities()
379       
380   
381    #Method for outputting model results
382    #FIXME: Split up into geometric and numeric stuff.
383    #FIXME: Geometric (X,Y,V) should live in mesh.py
384    #FIXME: STill remember to move XY to mesh
385    def get_vertex_values(self,
386                          xy=True, 
387                          smooth = None,
388                          precision = None,
389                          reduction = None):
390        """Return vertex values like an OBJ format
391
392        The vertex values are returned as one sequence in the 1D float array A.
393        If requested the coordinates will be returned in 1D arrays X and Y.
394       
395        The connectivity is represented as an integer array, V, of dimension
396        M x 3, where M is the number of volumes. Each row has three indices
397        into the X, Y, A arrays defining the triangle.
398
399        if smooth is True, vertex values corresponding to one common
400        coordinate set will be smoothed according to the given
401        reduction operator. In this case vertex coordinates will be
402        de-duplicated.
403       
404        If no smoothings is required, vertex coordinates and values will
405        be aggregated as a concatenation of values at
406        vertices 0, vertices 1 and vertices 2
407
408       
409        Calling convention
410        if xy is True:
411           X,Y,A,V = get_vertex_values
412        else:
413           A,V = get_vertex_values         
414           
415        """
416
417        from Numeric import concatenate, zeros, Float, Int, array, reshape
418
419
420        if smooth is None:
421            smooth = self.domain.smooth
422
423        if precision is None:
424            precision = Float
425
426        if reduction is None:
427            reduction = self.domain.reduction
428           
429        #Create connectivity
430                       
431        if smooth == True:
432
433            V = self.domain.get_vertices()
434            N = len(self.domain.vertexlist)
435            A = zeros(N, precision)
436
437            #Smoothing loop
438            for k in range(N):
439                L = self.domain.vertexlist[k]
440               
441                #Go through all triangle, vertex pairs
442                #contributing to vertex k and register vertex value
443
444                if L is None: continue #In case there are unused points
445               
446                contributions = []
447                for volume_id, vertex_id in L:
448                    v = self.vertex_values[volume_id, vertex_id]
449                    contributions.append(v)
450
451                A[k] = reduction(contributions)   
452
453
454            if xy is True:
455                X = self.domain.coordinates[:,0].astype(precision)
456                Y = self.domain.coordinates[:,1].astype(precision)
457               
458                return X, Y, A, V
459            else:
460                return A, V
461        else:
462            #Don't smooth
463
464            # Create a V like [[0 1 2], [3 4 5]....[3*m-2 3*m-1 3*m]]
465            # These vert_id's will relate to the verts created bellow
466            m = len(self.domain)  #Number of volumes
467            M = 3*m        #Total number of unique vertices
468            V = reshape(array(range(M)).astype(Int), (m,3))
469           
470            A = self.vertex_values.flat
471
472            #Do vertex coordinates   
473            if xy is True:               
474                C = self.domain.get_vertex_coordinates()
475
476                X = C[:,0:6:2].copy()
477                Y = C[:,1:6:2].copy()               
478
479                return X.flat, Y.flat, A, V           
480            else:
481                return A, V
482
483           
484    def extrapolate_first_order(self):
485        """Extrapolate conserved quantities from centroid to
486        vertices for each volume using
487        first order scheme.
488        """
489       
490        qc = self.centroid_values
491        qv = self.vertex_values
492
493        for i in range(3):
494            qv[:,i] = qc
495           
496
497
498class Conserved_quantity(Quantity):
499    """Class conserved quantity adds to Quantity:
500
501    boundary values, storage and method for updating, and
502    methods for (second order) extrapolation from centroid to vertices inluding
503    gradients and limiters
504    """
505
506    def __init__(self, domain, vertex_values=None):
507        Quantity.__init__(self, domain, vertex_values)
508       
509        from Numeric import zeros, Float
510
511        #Allocate space for boundary values
512        L = len(domain.boundary)
513        self.boundary_values = zeros(L, Float)
514
515        #Allocate space for updates of conserved quantities by
516        #flux calculations and forcing functions
517
518        N = domain.number_of_elements
519        self.explicit_update = zeros(N, Float )
520        self.semi_implicit_update = zeros(N, Float )
521               
522
523    def update(self, timestep):
524        #Call correct module function
525        #(either from this module or C-extension)
526        return update(self, timestep)
527
528
529    def compute_gradients(self):
530        #Call correct module function
531        #(either from this module or C-extension)
532        return compute_gradients(self)
533           
534
535    def limit(self):
536        #Call correct module function
537        #(either from this module or C-extension)
538        limit(self)
539       
540       
541    def extrapolate_second_order(self):
542        #Call correct module function
543        #(either from this module or C-extension)
544        extrapolate_second_order(self)
545
546
547def update(quantity, timestep):   
548    """Update centroid values based on values stored in
549    explicit_update and semi_implicit_update as well as given timestep
550
551    Function implementing forcing terms must take on argument
552    which is the domain and they must update either explicit
553    or implicit updates, e,g,:
554
555    def gravity(domain):
556        ....
557        domain.quantities['xmomentum'].explicit_update = ...
558        domain.quantities['ymomentum'].explicit_update = ...
559
560       
561
562    Explicit terms must have the form
563   
564        G(q, t)
565   
566    and explicit scheme is
567   
568       q^{(n+1}) = q^{(n)} + delta_t G(q^{n}, n delta_t)
569
570
571    Semi implicit forcing terms are assumed to have the form
572   
573       G(q, t) = H(q, t) q
574   
575    and the semi implicit scheme will then be
576   
577      q^{(n+1}) = q^{(n)} + delta_t H(q^{n}, n delta_t) q^{(n+1})
578
579   
580    """
581   
582    from Numeric import sum, equal, ones, Float
583       
584    N = quantity.centroid_values.shape[0]
585
586
587    #Divide H by conserved quantity to obtain G (see docstring above)
588
589
590    for k in range(N):
591        x = quantity.centroid_values[k] 
592        if x == 0.0:
593            #FIXME: Is this right
594            quantity.semi_implicit_update[k] = 0.0           
595        else:
596            quantity.semi_implicit_update[k] /= x             
597           
598    #Explicit updates
599    quantity.centroid_values += timestep*quantity.explicit_update
600           
601    #Semi implicit updates
602    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
603
604    if sum(equal(denominator, 0.0)) > 0.0:
605        msg = 'Zero division in semi implicit update. Call Stephen :-)'
606        raise msg
607    else:
608        #Update conserved_quantities from semi implicit updates
609        quantity.centroid_values /= denominator
610
611
612def interpolate_from_vertices_to_edges(quantity):
613    """Compute edge values from vertex values using linear interpolation
614    """
615
616    for k in range(quantity.vertex_values.shape[0]):
617        q0 = quantity.vertex_values[k, 0]
618        q1 = quantity.vertex_values[k, 1]
619        q2 = quantity.vertex_values[k, 2]
620           
621        quantity.edge_values[k, 0] = 0.5*(q1+q2)
622        quantity.edge_values[k, 1] = 0.5*(q0+q2) 
623        quantity.edge_values[k, 2] = 0.5*(q0+q1)
624
625
626
627def extrapolate_second_order(quantity):       
628    """Extrapolate conserved quantities from centroid to
629    vertices for each volume using
630    second order scheme.
631    """
632       
633    a, b = quantity.compute_gradients()
634
635    X = quantity.domain.get_vertex_coordinates()
636    qc = quantity.centroid_values
637    qv = quantity.vertex_values
638   
639    #Check each triangle
640    for k in range(quantity.domain.number_of_elements):
641        #Centroid coordinates           
642        x, y = quantity.domain.centroid_coordinates[k]
643       
644        #vertex coordinates
645        x0, y0, x1, y1, x2, y2 = X[k,:]
646       
647        #Extrapolate
648        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
649        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
650        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
651
652
653def compute_gradients(quantity):                   
654    """Compute gradients of triangle surfaces defined by centroids of
655    neighbouring volumes.
656    If one edge is on the boundary, use own centroid as neighbour centroid.
657    If two or more are on the boundary, fall back to first order scheme.
658    """
659
660    from Numeric import zeros, Float
661    from util import gradient
662   
663    centroid_coordinates = quantity.domain.centroid_coordinates
664    surrogate_neighbours = quantity.domain.surrogate_neighbours   
665    centroid_values = quantity.centroid_values   
666    number_of_boundaries = quantity.domain.number_of_boundaries     
667   
668    N = centroid_values.shape[0]
669
670    a = zeros(N, Float)
671    b = zeros(N, Float)
672   
673    for k in range(N):
674        if number_of_boundaries[k] < 2:
675            #Two or three true neighbours
676
677            #Get indices of neighbours (or self when used as surrogate)     
678            k0, k1, k2 = surrogate_neighbours[k,:]
679
680            #Get data       
681            q0 = centroid_values[k0]
682            q1 = centroid_values[k1]
683            q2 = centroid_values[k2]                   
684
685            x0, y0 = centroid_coordinates[k0] #V0 centroid
686            x1, y1 = centroid_coordinates[k1] #V1 centroid
687            x2, y2 = centroid_coordinates[k2] #V2 centroid
688
689            #Gradient
690            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
691       
692        elif number_of_boundaries[k] == 2:
693            #One true neighbour
694
695            #Get index of the one neighbour
696            for k0 in surrogate_neighbours[k,:]:
697                if k0 != k: break
698            assert k0 != k
699
700            k1 = k  #self
701
702            #Get data
703            q0 = centroid_values[k0]
704            q1 = centroid_values[k1]
705
706            x0, y0 = centroid_coordinates[k0] #V0 centroid
707            x1, y1 = centroid_coordinates[k1] #V1 centroid       
708
709            #Gradient
710            det = x0*y1 - x1*y0
711            if det != 0.0:
712                a[k] = (y1*q0 - y0*q1)/det
713                b[k] = (x0*q1 - x1*q0)/det
714
715        else:
716            #No true neighbours -       
717            #Fall back to first order scheme
718            pass
719       
720   
721    return a, b
722       
723                   
724
725def limit(quantity):       
726    """Limit slopes for each volume to eliminate artificial variance
727    introduced by e.g. second order extrapolator
728   
729    This is an unsophisticated limiter as it does not take into
730    account dependencies among quantities.
731   
732    precondition:
733    vertex values are estimated from gradient
734    postcondition:
735    vertex values are updated
736    """
737
738    from Numeric import zeros, Float
739
740    N = quantity.domain.number_of_elements
741   
742    beta = quantity.domain.beta
743       
744    qc = quantity.centroid_values
745    qv = quantity.vertex_values
746       
747    #Find min and max of this and neighbour's centroid values
748    qmax = zeros(qc.shape, Float)
749    qmin = zeros(qc.shape, Float)       
750   
751    for k in range(N):
752        qmax[k] = qmin[k] = qc[k]
753        for i in range(3):
754            n = quantity.domain.neighbours[k,i]
755            if n >= 0:
756                qn = qc[n] #Neighbour's centroid value
757               
758                qmin[k] = min(qmin[k], qn)
759                qmax[k] = max(qmax[k], qn)
760     
761   
762    #Diffences between centroids and maxima/minima
763    dqmax = qmax - qc
764    dqmin = qmin - qc
765       
766    #Deltas between vertex and centroid values
767    dq = zeros(qv.shape, Float)
768    for i in range(3):
769        dq[:,i] = qv[:,i] - qc
770
771    #Phi limiter   
772    for k in range(N):
773       
774        #Find the gradient limiter (phi) across vertices
775        phi = 1.0
776        for i in range(3):
777            r = 1.0
778            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
779            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
780           
781            phi = min( min(r*beta, 1), phi )   
782
783        #Then update using phi limiter
784        for i in range(3):           
785            qv[k,i] = qc[k] + phi*dq[k,i]
786
787
788
789import compile
790if compile.can_use_C_extension('quantity_ext.c'):
791    #Replace python version with c implementations
792       
793    from quantity_ext import limit, compute_gradients,\
794    extrapolate_second_order, interpolate_from_vertices_to_edges, update
795
Note: See TracBrowser for help on using the repository browser.