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

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

Finished and tested gradient and limiters
Added forcing terms (not tested yet)

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