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

Last change on this file since 255 was 255, checked in by ole, 20 years ago
File size: 14.2 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)
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
57    def interpolate(self):
58        """Compute interpolated values at edges and centroid
59        Pre-condition: vertex_values have been set
60        """
61
62        N = self.vertex_values.shape[0]       
63        for i in range(N):
64            v0 = self.vertex_values[i, 0]
65            v1 = self.vertex_values[i, 1]
66            v2 = self.vertex_values[i, 2]
67           
68            self.centroid_values[i] = (v0 + v1 + v2)/3
69
70        self.interpolate_from_vertices_to_edges()
71
72
73    def interpolate_from_vertices_to_edges(self):
74        for k in range(self.vertex_values.shape[0]):
75            q0 = self.vertex_values[k, 0]
76            q1 = self.vertex_values[k, 1]
77            q2 = self.vertex_values[k, 2]
78           
79            self.edge_values[k, 0] = 0.5*(q1+q2)
80            self.edge_values[k, 1] = 0.5*(q0+q2) 
81            self.edge_values[k, 2] = 0.5*(q0+q1)
82
83           
84           
85    def set_values(self, X, location='vertices'):
86        """Set values for quantity
87
88        X: Compatible list, Numeric array (see below), constant or function
89        location: Where values are to be stored.
90                  Permissible options are: vertices, edges, centroid
91                  Default is "vertices"
92
93        In case of location == 'centroid' the dimension values must
94        be a list of a Numerical array of length N, N being the number
95        of elements in the mesh. Otherwise it must be of dimension Nx3
96
97        The values will be stored in elements following their
98        internal ordering.
99
100        If values are described a function, it will be evaluated at specified points
101
102        If selected location is vertices, values for centroid and edges
103        will be assigned interpolated values.
104        In any other case, only values for the specified locations
105        will be assigned and the others will be left undefined.
106        """
107
108        if location not in ['vertices', 'centroids', 'edges']:
109            msg = 'Invalid location: %s' %location
110            raise msg
111
112        if X is None:
113            msg = 'Given values are None'
114            raise msg           
115       
116        import types
117       
118        if callable(X):
119            #Use function specific method
120            self.set_function_values(X, location)           
121        elif type(X) in [types.FloatType, types.IntType, types.LongType]:
122            if location == 'centroids':
123                self.centroid_values[:] = X
124            elif location == 'edges':
125                self.edge_values[:] = X
126            else:
127                self.vertex_values[:] = X               
128
129        else:
130            #Use array specific method
131            self.set_array_values(X, location)
132
133        if location == 'vertices':
134            #Intialise centroid and edge_values
135            self.interpolate()
136           
137
138
139    def set_function_values(self, f, location='vertices'):
140        """Set values for quantity using specified function
141
142        f: x, y -> z Function where x, y and z are arrays
143        location: Where values are to be stored.
144                  Permissible options are: vertices, edges, centroid
145                  Default is "vertices"
146        """
147
148        if location == 'centroids':
149            P = self.domain.centroids
150            self.set_values(f(P[:,0], P[:,1]), location)
151        elif location == 'edges':
152            raise 'Not implemented: %s' %location
153        else:
154            #Vertices
155            P = self.domain.get_vertex_coordinates()
156            for i in range(3):
157                self.vertex_values[:,i] = f(P[:,2*i], P[:,2*i+1])
158
159           
160    def set_array_values(self, values, location='vertices'):
161        """Set values for quantity
162
163        values: Numeric array
164        location: Where values are to be stored.
165                  Permissible options are: vertices, edges, centroid
166                  Default is "vertices"
167
168        In case of location == 'centroid' the dimension values must
169        be a list of a Numerical array of length N, N being the number
170        of elements in the mesh. Otherwise it must be of dimension Nx3
171
172        The values will be stored in elements following their
173        internal ordering.
174
175        If selected location is vertices, values for centroid and edges
176        will be assigned interpolated values.
177        In any other case, only values for the specified locations
178        will be assigned and the others will be left undefined.
179        """
180
181        from Numeric import array, Float
182
183        values = array(values).astype(Float)
184
185        N = self.centroid_values.shape[0]
186       
187        msg = 'Number of values must match number of elements'
188        assert values.shape[0] == N, msg
189       
190        if location == 'centroids':
191            assert len(values.shape) == 1, 'Values array must be 1d'
192            self.centroid_values = values                       
193        elif location == 'edges':
194            assert len(values.shape) == 2, 'Values array must be 2d'
195            msg = 'Array must be N x 3'           
196            assert values.shape[1] == 3, msg
197           
198            self.edge_values = values
199        else:
200            assert len(values.shape) == 2, 'Values array must be 2d'
201            msg = 'Array must be N x 3'           
202            assert values.shape[1] == 3, msg
203           
204            self.vertex_values = values
205
206
207
208class Conserved_quantity(Quantity):
209    """Class conserved quantity adds to Quantity:
210
211    boundary values, storage and method for updating, and
212    methods for extrapolation from centropid to vertices inluding
213    gradients and limiters
214    """
215
216    def __init__(self, domain, vertex_values=None):
217        Quantity.__init__(self, domain, vertex_values)
218       
219        from Numeric import zeros, Float
220
221        #Allocate space for boundary values
222        L = len(domain.boundary)
223        self.boundary_values = zeros(L, Float)
224
225        #Allocate space for updates of conserved quantities by
226        #flux calculations and forcing functions
227
228        N = domain.number_of_elements
229        self.explicit_update = zeros(N, Float )
230        self.semi_implicit_update = zeros(N, Float )
231               
232
233    def update(self, timestep):
234        """Update centroid values based on values stored in
235        explicit_update and semi_implicit_update as well as given timestep
236        """
237
238        from Numeric import sum, equal, ones, Float
239       
240        N = self.centroid_values.shape[0]
241       
242        #Explicit updates
243        self.centroid_values += timestep*self.explicit_update
244           
245        #Semi implicit updates
246        denominator = ones(N, Float)-timestep*self.semi_implicit_update
247
248        if sum(equal(denominator, 0.0)) > 0.0:
249            msg = 'Zero division in semi implicit update. Call Stephen :-)'
250            raise msg
251        else:
252            #Update conserved_quantities from semi implicit updates
253            self.centroid_values /= denominator
254
255
256    def compute_gradients(self):
257        """Compute gradients of triangle surfaces defined by centroids of
258        neighbouring volumes.
259        If one face is on the boundary, use own centroid as neighbour centroid.
260        If two or more are on the boundary, fall back to first order scheme.
261       
262        Also return minimum and maximum of conserved quantities
263        """
264
265       
266        from Numeric import array, zeros, Float
267        from util import gradient
268
269        N = self.centroid_values.shape[0]
270
271        a = zeros(N, Float)
272        b = zeros(N, Float)
273       
274        for k in range(N):
275       
276            number_of_boundaries = self.domain.number_of_boundaries[k]
277
278            if number_of_boundaries == 3:                 
279                #We have zero neighbouring volumes -
280                #Fall back to first order scheme
281                pass
282           
283            elif number_of_boundaries == 2:
284                #Special case where we have only one neighbouring volume.
285
286                #Find index of the one neighbour
287                for k0 in self.domain.neighbours[k,:]:
288                    if k0 >= 0:
289                        break
290
291                assert k0 != k
292                assert k0 >= 0
293
294                k1 = k  #Self
295
296                #Get data
297                q0 = self.centroid_values[k0]
298                q1 = self.centroid_values[k1]
299
300                x0, y0 = self.domain.centroids[k0] #V0 centroid
301                x1, y1 = self.domain.centroids[k1] #V1 centroid       
302
303                #Gradient
304                det = x0*y1 - x1*y0
305                if det != 0.0:
306                    a[k] = (y1*q0 - y0*q1)/det
307                    b[k] = (x0*q1 - x1*q0)/det
308
309            else:
310                #One or zero missing neighbours
311                #In case of one boundary - own centroid
312                #has been inserted as a surrogate for the one
313                #missing neighbour in neighbour_surrogates
314
315                #Get data       
316                k0 = self.domain.surrogate_neighbours[k,0]
317                k1 = self.domain.surrogate_neighbours[k,1]
318                k2 = self.domain.surrogate_neighbours[k,2]
319
320                q0 = self.centroid_values[k0]
321                q1 = self.centroid_values[k1]
322                q2 = self.centroid_values[k2]                   
323
324                x0, y0 = self.domain.centroids[k0] #V0 centroid
325                x1, y1 = self.domain.centroids[k1] #V1 centroid
326                x2, y2 = self.domain.centroids[k2] #V2 centroid
327
328                #Gradient
329                a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
330       
331        return a, b
332       
333
334    def limit(self):
335        #Call correct module function
336        #(either from this module or C-extension)
337        limit(self)
338       
339       
340    def extrapolate_first_order(self):
341        """Extrapolate conserved quantities from centroid to
342        vertices for each volume using
343        first order scheme.
344        """
345       
346        qc = self.centroid_values
347        qv = self.vertex_values
348
349        for i in range(3):
350            qv[:,i] = qc
351           
352           
353    def extrapolate_second_order(self):
354        #Call correct module function
355        #(either from this module or C-extension)
356        extrapolate_second_order(self)
357
358
359def extrapolate_second_order(self):       
360    """Extrapolate conserved quantities from centroid to
361    vertices for each volume using
362    second order scheme.
363    """
364       
365    a, b = self.compute_gradients()
366
367    V = self.domain.get_vertex_coordinates()
368    qc = self.centroid_values
369    qv = self.vertex_values
370   
371    #Check each triangle
372    for k in range(self.domain.number_of_elements):
373        #Centroid coordinates           
374        x, y = self.domain.centroids[k]
375       
376        #vertex coordinates
377        x0, y0, x1, y1, x2, y2 = V[k,:]
378       
379        #Extrapolate
380        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
381        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
382        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
383
384           
385
386def limit(quantity):       
387    """Limit slopes for each volume to eliminate artificial variance
388    introduced by e.g. second order extrapolator
389   
390    This is an unsophisticated limiter as it does not take into
391    account dependencies among quantities.
392   
393    precondition:
394    vertex values are estimated from gradient
395    postcondition:
396    vertex values are updated
397    """
398
399    from Numeric import zeros, Float
400
401    N = quantity.domain.number_of_elements
402   
403    beta = quantity.domain.beta
404       
405    qc = quantity.centroid_values
406    qv = quantity.vertex_values
407       
408    #Find min and max of this and neighbour's centroid values
409    qmax = zeros(qc.shape, Float)
410    qmin = zeros(qc.shape, Float)       
411   
412    for k in range(N):
413        qmax[k] = qmin[k] = qc[k]
414        for i in range(3):
415            n = quantity.domain.neighbours[k,i]
416            if n >= 0:
417                qn = qc[n] #Neighbour's centroid value
418               
419                qmin[k] = min(qmin[k], qn)
420                qmax[k] = max(qmax[k], qn)
421     
422   
423    #Diffences between centroids and maxima/minima
424    dqmax = qmax - qc
425    dqmin = qmin - qc
426       
427    #Deltas between vertex and centroid values
428    dq = zeros(qv.shape, Float)
429    for i in range(3):
430        dq[:,i] = qv[:,i] - qc
431
432    #Phi limiter   
433    for k in range(N):
434       
435        #Find the gradient limiter (phi) across vertices
436        phi = 1.0
437        for i in range(3):
438            r = 1.0
439            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
440            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
441           
442            phi = min( min(r*beta, 1), phi )   
443
444        #Then update using phi limiter
445        for i in range(3):           
446            qv[k,i] = qc[k] + phi*dq[k,i]
447
448
449
450import compile
451if compile.can_use_C_extension('quantity_ext.c'):
452    #Replace python version with c implementations
453
454    from quantity_ext import limit #, extrapolate_second_order       
Note: See TracBrowser for help on using the repository browser.