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

Last change on this file since 269 was 265, checked in by ole, 21 years ago

Interpoalte_from_vertices_to_edges - implemented in C

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