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

Last change on this file since 298 was 297, checked in by ole, 21 years ago

Adapted original pmesh_to_domain

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