source: inundation/ga/storm_surge/pyvolution-1d/quantity.py @ 989

Last change on this file since 989 was 989, checked in by steve, 20 years ago

Corrected h limited update to ensure conservation.

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