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

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

Finally quashed bug where ymomentum wasn't updated in
second order limiter

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