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

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

testing of set_value and a bit of name changing

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