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

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

Compute_gradients c implementation

File size: 13.9 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        #Call correct module function
258        #(either from this module or C-extension)
259        return compute_gradients(self)
260           
261
262    def limit(self):
263        #Call correct module function
264        #(either from this module or C-extension)
265        limit(self)
266       
267       
268    def extrapolate_first_order(self):
269        """Extrapolate conserved quantities from centroid to
270        vertices for each volume using
271        first order scheme.
272        """
273       
274        qc = self.centroid_values
275        qv = self.vertex_values
276
277        for i in range(3):
278            qv[:,i] = qc
279           
280           
281    def extrapolate_second_order(self):
282        #Call correct module function
283        #(either from this module or C-extension)
284        extrapolate_second_order(self)
285
286
287def extrapolate_second_order(self):       
288    """Extrapolate conserved quantities from centroid to
289    vertices for each volume using
290    second order scheme.
291    """
292       
293    a, b = self.compute_gradients()
294
295    X = self.domain.get_vertex_coordinates()
296    qc = self.centroid_values
297    qv = self.vertex_values
298   
299    #Check each triangle
300    for k in range(self.domain.number_of_elements):
301        #Centroid coordinates           
302        x, y = self.domain.centroids[k]
303       
304        #vertex coordinates
305        x0, y0, x1, y1, x2, y2 = X[k,:]
306       
307        #Extrapolate
308        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
309        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
310        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
311
312
313def compute_gradients(quantity):                   
314    """Compute gradients of triangle surfaces defined by centroids of
315    neighbouring volumes.
316    If one edge is on the boundary, use own centroid as neighbour centroid.
317    If two or more are on the boundary, fall back to first order scheme.
318    """
319
320    from Numeric import zeros, Float
321    from util import gradient
322   
323    centroids = quantity.domain.centroids
324    surrogate_neighbours = quantity.domain.surrogate_neighbours   
325    centroid_values = quantity.centroid_values   
326    number_of_boundaries = quantity.domain.number_of_boundaries     
327   
328    N = centroid_values.shape[0]
329
330    a = zeros(N, Float)
331    b = zeros(N, Float)
332   
333    for k in range(N):
334        if number_of_boundaries[k] < 2:
335            #Two or three true neighbours
336
337            #Get indices of neighbours (or self when used as surrogate)     
338            k0, k1, k2 = surrogate_neighbours[k,:]
339
340            #Get data       
341            q0 = centroid_values[k0]
342            q1 = centroid_values[k1]
343            q2 = centroid_values[k2]                   
344
345            x0, y0 = centroids[k0] #V0 centroid
346            x1, y1 = centroids[k1] #V1 centroid
347            x2, y2 = centroids[k2] #V2 centroid
348
349            #Gradient
350            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
351       
352        elif number_of_boundaries[k] == 2:
353            #One true neighbour
354
355            #Get index of the one neighbour
356            for k0 in surrogate_neighbours[k,:]:
357                if k0 != k: break
358            assert k0 != k
359
360            k1 = k  #self
361
362            #Get data
363            q0 = centroid_values[k0]
364            q1 = centroid_values[k1]
365
366            x0, y0 = centroids[k0] #V0 centroid
367            x1, y1 = centroids[k1] #V1 centroid       
368
369            #Gradient
370            det = x0*y1 - x1*y0
371            if det != 0.0:
372                a[k] = (y1*q0 - y0*q1)/det
373                b[k] = (x0*q1 - x1*q0)/det
374
375        else:
376            #No true neighbours -       
377            #Fall back to first order scheme
378            pass
379       
380   
381    return a, b
382       
383                   
384
385def limit(quantity):       
386    """Limit slopes for each volume to eliminate artificial variance
387    introduced by e.g. second order extrapolator
388   
389    This is an unsophisticated limiter as it does not take into
390    account dependencies among quantities.
391   
392    precondition:
393    vertex values are estimated from gradient
394    postcondition:
395    vertex values are updated
396    """
397
398    from Numeric import zeros, Float
399
400    N = quantity.domain.number_of_elements
401   
402    beta = quantity.domain.beta
403       
404    qc = quantity.centroid_values
405    qv = quantity.vertex_values
406       
407    #Find min and max of this and neighbour's centroid values
408    qmax = zeros(qc.shape, Float)
409    qmin = zeros(qc.shape, Float)       
410   
411    for k in range(N):
412        qmax[k] = qmin[k] = qc[k]
413        for i in range(3):
414            n = quantity.domain.neighbours[k,i]
415            if n >= 0:
416                qn = qc[n] #Neighbour's centroid value
417               
418                qmin[k] = min(qmin[k], qn)
419                qmax[k] = max(qmax[k], qn)
420     
421   
422    #Diffences between centroids and maxima/minima
423    dqmax = qmax - qc
424    dqmin = qmin - qc
425       
426    #Deltas between vertex and centroid values
427    dq = zeros(qv.shape, Float)
428    for i in range(3):
429        dq[:,i] = qv[:,i] - qc
430
431    #Phi limiter   
432    for k in range(N):
433       
434        #Find the gradient limiter (phi) across vertices
435        phi = 1.0
436        for i in range(3):
437            r = 1.0
438            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
439            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
440           
441            phi = min( min(r*beta, 1), phi )   
442
443        #Then update using phi limiter
444        for i in range(3):           
445            qv[k,i] = qc[k] + phi*dq[k,i]
446
447
448
449import compile
450if compile.can_use_C_extension('quantity_ext.c'):
451    #Replace python version with c implementations
452       
453    from quantity_ext import limit, compute_gradients #, extrapolate_second_order       
Note: See TracBrowser for help on using the repository browser.