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

Last change on this file since 436 was 389, checked in by duncan, 20 years ago

add vert atts to pmesh2domain/pyvolution

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