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

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

Quantities Working

File size: 13.2 KB
RevLine 
[256]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
[279]52        #Intialise centroid values
[256]53        self.interpolate()
[279]54       
[256]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
[279]78        of elements in the mesh. Otherwise it must be of dimension Nx3
[256]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
[279]85        If selected location is vertices, values for centroid and edges
[256]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']:
[279]92            msg = 'Invalid location: %s, (possible choices vertices, centroids)' %location
[256]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               
[279]109
[256]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
[279]120
[256]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
[279]135            P = self.domain.get_vertices()
[256]136            for i in range(2):
[279]137                self.vertex_values[:,i] = f(P[:,i])
[256]138
139
[279]140    def set_array_values(self, values, location='vertices'):
141        """Set values for quantity
[256]142
[279]143        values: Numeric array
144        location: Where values are to be stored.
145                  Permissible options are: vertices, centroid
146                  Default is "vertices"
[256]147
[279]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
[256]151
[279]152        The values will be stored in elements following their
153        internal ordering.
[256]154
[279]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        """
[256]160
[279]161        from Numeric import array, Float
[256]162
[279]163        values = array(values).astype(Float)
164
165        N = self.centroid_values.shape[0]
[256]166       
[279]167        msg = 'Number of values must match number of elements'
168        assert values.shape[0] == N, msg
[256]169       
[279]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
[256]177           
[279]178            self.vertex_values = values
179
180
181
182
[296]183class Conserved_quantity(Quantity):
184    """Class conserved quantity adds to Quantity:
[279]185
[296]186    storage and method for updating, and
187    methods for extrapolation from centropid to vertices inluding
188    gradients and limiters
189    """
[279]190
[296]191    def __init__(self, domain, vertex_values=None):
192        Quantity.__init__(self, domain, vertex_values)
[256]193       
[296]194        from Numeric import zeros, Float
[256]195
[296]196        #Allocate space for updates of conserved quantities by
197        #flux calculations and forcing functions
[256]198
[296]199        N = domain.number_of_elements
200        self.explicit_update = zeros(N, Float )
201        self.semi_implicit_update = zeros(N, Float )
[335]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)             
[256]206               
207
[296]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        """
[256]212
[296]213        from Numeric import sum, equal, ones, Float
[256]214       
[296]215        N = self.centroid_values.shape[0]
[256]216       
[296]217        #Explicit updates
218        self.centroid_values += timestep*self.explicit_update
[256]219           
[296]220        #Semi implicit updates
221        denominator = ones(N, Float)-timestep*self.semi_implicit_update
[256]222
[296]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
[256]229
230
[296]231    def compute_gradients(self):
[335]232        """Compute gradients of piecewise linear function defined by centroids of
[296]233        neighbouring volumes.
234        """
[256]235
236       
[296]237        from Numeric import array, zeros, Float
[256]238
[296]239        N = self.centroid_values.shape[0]
[256]240
[335]241
242        G = self.gradients
243        Q = self.centroid_values
244        X = self.domain.centroids
[256]245       
[296]246        for k in range(N):
[256]247
[296]248            # first and last elements have boundaries
[256]249
[335]250            if k == 0:
[256]251
[296]252                #Get data
253                k0 = k
254                k1 = k+1
255               
[335]256                q0 = Q[k0]
257                q1 = Q[k1]
[256]258
[335]259                x0 = X[k0] #V0 centroid
260                x1 = X[k1] #V1 centroid       
[256]261
[296]262                #Gradient
[335]263                G[k] = (q1 - q0)/(x1 - x0)
[256]264
[296]265            elif k == N-1:
266               
267                #Get data
268                k0 = k
269                k1 = k-1
270               
[335]271                q0 = Q[k0]
272                q1 = Q[k1]
[256]273
[335]274                x0 = X[k0] #V0 centroid
275                x1 = X[k1] #V1 centroid       
[256]276
[296]277                #Gradient
[335]278                G[k] = (q1 - q0)/(x1 - x0)
[296]279               
[335]280            else:
[296]281                #Interior Volume (2 neighbours)
[256]282
[296]283                #Get data
[335]284                k0 = k-1
285                k2 = k+1
[256]286
[335]287                q0 = Q[k0]
288                q1 = Q[k]
289                q2 = Q[k2]                   
[256]290
[335]291                x0 = X[k0] #V0 centroid
292                x1 = X[k] #V1 centroid (Self)
293                x2 = X[k2] #V2 centroid
[256]294
[296]295                #Gradient
[335]296                G[k] = ((q0-q1)/(x0-x1)*(x2-x1) - (q2-q1)/(x2-x1)*(x0-x1))/(x2-x0)
[256]297       
[335]298        return
[256]299       
[335]300    def extrapolate_first_order(self):
301        """Extrapolate conserved quantities from centroid to
302        vertices for each volume using
303        first order scheme.
304        """
[256]305       
[335]306        qc = self.centroid_values
307        qv = self.vertex_values
[256]308
[335]309        for i in range(2):
310            qv[:,i] = qc
[256]311           
312           
[335]313    def extrapolate_second_order(self):
314        """Extrapolate conserved quantities from centroid to
315        vertices for each volume using
316        second order scheme.
317        """
[256]318       
[335]319        self.compute_gradients()
[256]320
[335]321        G = self.gradients
322        V = self.domain.vertices
323        Qc = self.centroid_values
324        Qv = self.vertex_values
[256]325       
[335]326        #Check each triangle
327        for k in range(self.domain.number_of_elements):
328            #Centroid coordinates           
329            x = self.domain.centroids[k]
[256]330
[335]331            #vertex coordinates
332            x0, x1 = V[k,:]
[256]333
[335]334            #Extrapolate
335            Qv[k,0] = Qc[k] + G[k]*(x0-x)
336            Qv[k,1] = Qc[k] + G[k]*(x1-x)
[256]337
[335]338
[256]339           
340
[335]341    def limit(self):       
342        """Limit slopes for each volume to eliminate artificial variance
343        introduced by e.g. second order extrapolator
[256]344
[335]345        This is an unsophisticated limiter as it does not take into
346        account dependencies among quantities.
[256]347
[335]348        precondition:
349        vertex values are estimated from gradient
350        postcondition:
351        vertex values are updated
352        """
[256]353
[335]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]
[256]368           
[335]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
[256]373
[335]374                    qmin[k] = min(qmin[k], qn)
375                    qmax[k] = max(qmax[k], qn)
[256]376
[335]377        #Phi limiter   
378        for k in range(N):
[256]379
[335]380            #Diffences between centroids and maxima/minima
381            dqmax = qmax[k] - qc[k] 
382            dqmin = qmin[k] - qc[k]
[296]383
[335]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
[296]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
[513]412    Q1 = Conserved_quantity(D1, vertex_values)
[296]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
[513]437
[335]438    Xc = Q1.domain.vertices
439    Qc = Q1.vertex_values
440    print Xc
441    print Qc
[296]442
[335]443    Qc[1,0] = 3
[513]444
445    Q1.extrapolate_second_order()
446    Q1.limit()
447   
[335]448    from matplotlib.matlab import *
449    plot(Xc,Qc)
[296]450
[438]451    show()
452
[513]453   
[438]454
455
[513]456
[335]457   
458   
[296]459
[335]460   
[296]461
[335]462
463
464
Note: See TracBrowser for help on using the repository browser.