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

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

Got datamanager for sww smooth to work.
Non-smooth does not work yet and tests are unfinished

File size: 18.9 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
18class Quantity:
19
20    def __init__(self, domain, vertex_values=None):
21
22        from mesh import Mesh
23        from Numeric import array, zeros, Float
24
25        msg = 'First argument in Quantity.__init__ '
26        msg += 'must be of class Mesh (or a subclass thereof)'
27        assert isinstance(domain, Mesh), msg
28
29        if vertex_values is None:
30            N = domain.number_of_elements           
31            self.vertex_values = zeros((N, 3), Float)
32        else:   
33            self.vertex_values = array(vertex_values).astype(Float)
34
35            N, V = self.vertex_values.shape
36            assert V == 3,\
37                   'Three 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        self.edge_values = zeros((N, 3), Float)       
52
53        #Intialise centroid and edge_values
54        self.interpolate()
55
56    def __len__(self):
57        return self.centroid_values.shape[0]
58       
59    def interpolate(self):
60        """Compute interpolated values at edges and centroid
61        Pre-condition: vertex_values have been set
62        """
63
64        N = self.vertex_values.shape[0]       
65        for i in range(N):
66            v0 = self.vertex_values[i, 0]
67            v1 = self.vertex_values[i, 1]
68            v2 = self.vertex_values[i, 2]
69           
70            self.centroid_values[i] = (v0 + v1 + v2)/3
71
72        self.interpolate_from_vertices_to_edges()
73
74
75    def interpolate_from_vertices_to_edges(self):
76        #Call correct module function
77        #(either from this module or C-extension)
78        interpolate_from_vertices_to_edges(self)
79           
80           
81    def set_values(self, X, location='vertices'):
82        """Set values for quantity
83
84        X: Compatible list, Numeric array (see below), constant or function
85        location: Where values are to be stored.
86                  Permissible options are: vertices, edges, centroid
87                  Default is "vertices"
88
89        In case of location == 'centroid' the dimension values must
90        be a list of a Numerical array of length N, N being the number
91        of elements in the mesh. Otherwise it must be of dimension Nx3
92
93        The values will be stored in elements following their
94        internal ordering.
95
96        If values are described a function, it will be evaluated at specified points
97
98        If selected location is vertices, values for centroid and edges
99        will be assigned interpolated values.
100        In any other case, only values for the specified locations
101        will be assigned and the others will be left undefined.
102        """
103
104        if location not in ['vertices', 'centroids', 'edges']:
105            msg = 'Invalid location: %s' %location
106            raise msg
107
108        if X is None:
109            msg = 'Given values are None'
110            raise msg           
111       
112        import types
113       
114        if callable(X):
115            #Use function specific method
116            self.set_function_values(X, location)           
117        elif type(X) in [types.FloatType, types.IntType, types.LongType]:
118            if location == 'centroids':
119                self.centroid_values[:] = X
120            elif location == 'edges':
121                self.edge_values[:] = X
122            else:
123                self.vertex_values[:] = X               
124
125        else:
126            #Use array specific method
127            self.set_array_values(X, location)
128
129        if location == 'vertices':
130            #Intialise centroid and edge_values
131            self.interpolate()
132           
133
134
135    def set_function_values(self, f, location='vertices'):
136        """Set values for quantity using specified function
137
138        f: x, y -> z Function where x, y and z are arrays
139        location: Where values are to be stored.
140                  Permissible options are: vertices, edges, centroid
141                  Default is "vertices"
142        """
143
144        if location == 'centroids':
145            P = self.domain.centroids
146            self.set_values(f(P[:,0], P[:,1]), location)
147        elif location == 'edges':
148            raise 'Not implemented: %s' %location
149        else:
150            #Vertices
151            P = self.domain.get_vertex_coordinates()
152            for i in range(3):
153                self.vertex_values[:,i] = f(P[:,2*i], P[:,2*i+1])
154
155           
156    def set_array_values(self, values, location='vertices'):
157        """Set values for quantity
158
159        values: Numeric array
160        location: Where values are to be stored.
161                  Permissible options are: vertices, edges, centroid
162                  Default is "vertices"
163
164        In case of location == 'centroid' the dimension values must
165        be a list of a Numerical array of length N, N being the number
166        of elements in the mesh. Otherwise it must be of dimension Nx3
167
168        The values will be stored in elements following their
169        internal ordering.
170
171        If selected location is vertices, values for centroid and edges
172        will be assigned interpolated values.
173        In any other case, only values for the specified locations
174        will be assigned and the others will be left undefined.
175        """
176
177        from Numeric import array, Float
178
179        values = array(values).astype(Float)
180
181        N = self.centroid_values.shape[0]
182       
183        msg = 'Number of values must match number of elements'
184        assert values.shape[0] == N, msg
185       
186        if location == 'centroids':
187            assert len(values.shape) == 1, 'Values array must be 1d'
188            self.centroid_values = values                       
189        elif location == 'edges':
190            assert len(values.shape) == 2, 'Values array must be 2d'
191            msg = 'Array must be N x 3'           
192            assert values.shape[1] == 3, msg
193           
194            self.edge_values = values
195        else:
196            assert len(values.shape) == 2, 'Values array must be 2d'
197            msg = 'Array must be N x 3'           
198            assert values.shape[1] == 3, msg
199           
200            self.vertex_values = values
201
202
203
204   
205    def smooth_vertex_values(self, value_array='field_values',
206                             precision = None):
207        """ Smooths field_values or conserved_quantities data.
208        TODO: be able to smooth individual fields
209        NOTE:  This function does not have a test.
210        FIXME: NOT DONE
211        """
212
213        from Numeric import concatenate, zeros, Float, Int, array, reshape
214
215       
216        A,V = self.get_vertex_values(xy=False,
217                                     value_array=value_array,
218                                     smooth = True,
219                                     precision = precision)
220
221        #Set some field values
222        for volume in self:
223            for i,v in enumerate(volume.vertices):
224                if value_array == 'field_values':
225                    volume.set_field_values('vertex', i, A[v,:])
226                elif value_array == 'conserved_quantities':
227                    volume.set_conserved_quantities('vertex', i, A[v,:])
228
229        if value_array == 'field_values':
230            self.precompute()
231        elif value_array == 'conserved_quantities':
232            Volume.interpolate_conserved_quantities()
233       
234   
235    #Method for outputting model results
236    def get_vertex_values(self,
237                          xy=True, 
238                          smooth = None,
239                          precision = None,
240                          reduction = None):
241        """Return vertex values like an OBJ format
242
243        The vertex values are returned as one sequence in the 1D float array A.
244        If requested the coordinates will be returned in 1D arrays X and Y.
245       
246        The connectivity is represented as an integer array, V, of dimension
247        M x 3, where M is the number of volumes. Each row has three indices
248        into the X, Y, A arrays defining the triangle.
249
250        if smooth is True, vertex values corresponding to one common
251        coordinate set will be smoothed according to the given
252        reduction operator. In this case vertex coordinates will be
253        de-duplicated.
254       
255        If no smoothings is required, vertex coordinates and values will
256        be aggregated as a concatenation of of values at
257        vertices 0, vertices 1 and vertices 2
258
259       
260        Calling convention
261        if xy is True:
262           X,Y,A,V = get_vertex_values
263        else:
264           A,V = get_vertex_values         
265           
266        """
267
268        from Numeric import concatenate, zeros, Float, Int, array, reshape
269
270
271        if smooth is None:
272            smooth = self.domain.smooth
273
274        if precision is None:
275            precision = Float
276
277        if reduction is None:
278            reduction = self.domain.reduction
279
280        if smooth == True:
281
282            N = len(self.domain.vertexlist)
283            A = zeros(N, precision)
284            V = self.domain.vertices
285           
286            #Smoothing loop
287            for k in range(N):
288                L = self.domain.vertexlist[k]
289               
290                #Go through all triangle, vertex pairs
291                #contributing to vertex k and register vertex value
292                contributions = []               
293                for volume_id, vertex_id in L:
294
295                    v = self.vertex_values[volume_id, vertex_id]
296                    contributions.append(v)
297
298                A[k] = reduction(contributions)   
299
300
301            if xy is True:
302                X = self.domain.coordinates[:,0].astype(precision)
303                Y = self.domain.coordinates[:,1].astype(precision)
304               
305                return X, Y, A, V
306            else:
307                return A, V
308        else:
309            #Don't smooth
310
311            m = len(self)  #Number of volumes
312            M = 3*m        #Total number of unique vertices
313
314            A = self.vertex_values.flat
315
316            #Create connectivity
317            tmp = reshape(array(range(m)).astype(Int), (m,1))
318            V = concatenate( (tmp, tmp+m, tmp+2*m), axis = 1 )
319
320            #Do vertex coordinates   
321            if xy is True:               
322                C = self.domain.get_vertex_coordinates()
323
324                #X = C[:,0:6:2].copy()
325                #Y = C[:,1:6:2].copy()               
326
327                X = concatenate((C[:,0], C[:,2], C[:,4]), 
328                                axis=0).astype(precision)
329                Y = concatenate((C[:,1], C[:,3], C[:,5]), 
330                                axis=0).astype(precision)
331               
332               
333               
334                return X.flat, Y.flat, A, V           
335            else:
336                return A, V
337
338           
339           
340
341
342class Conserved_quantity(Quantity):
343    """Class conserved quantity adds to Quantity:
344
345    boundary values, storage and method for updating, and
346    methods for extrapolation from centropid to vertices inluding
347    gradients and limiters
348    """
349
350    def __init__(self, domain, vertex_values=None):
351        Quantity.__init__(self, domain, vertex_values)
352       
353        from Numeric import zeros, Float
354
355        #Allocate space for boundary values
356        L = len(domain.boundary)
357        self.boundary_values = zeros(L, Float)
358
359        #Allocate space for updates of conserved quantities by
360        #flux calculations and forcing functions
361
362        N = domain.number_of_elements
363        self.explicit_update = zeros(N, Float )
364        self.semi_implicit_update = zeros(N, Float )
365               
366
367    def update(self, timestep):
368        #Call correct module function
369        #(either from this module or C-extension)
370        return update(self, timestep)
371
372
373    def compute_gradients(self):
374        #Call correct module function
375        #(either from this module or C-extension)
376        return compute_gradients(self)
377           
378
379    def limit(self):
380        #Call correct module function
381        #(either from this module or C-extension)
382        limit(self)
383       
384       
385    def extrapolate_first_order(self):
386        """Extrapolate conserved quantities from centroid to
387        vertices for each volume using
388        first order scheme.
389        """
390       
391        qc = self.centroid_values
392        qv = self.vertex_values
393
394        for i in range(3):
395            qv[:,i] = qc
396           
397           
398    def extrapolate_second_order(self):
399        #Call correct module function
400        #(either from this module or C-extension)
401        extrapolate_second_order(self)
402
403
404def update(quantity, timestep):   
405    """Update centroid values based on values stored in
406    explicit_update and semi_implicit_update as well as given timestep
407    """
408   
409    from Numeric import sum, equal, ones, Float
410       
411    N = quantity.centroid_values.shape[0]
412       
413    #Explicit updates
414    quantity.centroid_values += timestep*quantity.explicit_update
415           
416    #Semi implicit updates
417    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
418
419    if sum(equal(denominator, 0.0)) > 0.0:
420        msg = 'Zero division in semi implicit update. Call Stephen :-)'
421        raise msg
422    else:
423        #Update conserved_quantities from semi implicit updates
424        quantity.centroid_values /= denominator
425
426
427def interpolate_from_vertices_to_edges(quantity):
428    """Compute edge values from vertex values using linear interpolation
429    """
430
431    for k in range(quantity.vertex_values.shape[0]):
432        q0 = quantity.vertex_values[k, 0]
433        q1 = quantity.vertex_values[k, 1]
434        q2 = quantity.vertex_values[k, 2]
435           
436        quantity.edge_values[k, 0] = 0.5*(q1+q2)
437        quantity.edge_values[k, 1] = 0.5*(q0+q2) 
438        quantity.edge_values[k, 2] = 0.5*(q0+q1)
439
440
441
442def extrapolate_second_order(quantity):       
443    """Extrapolate conserved quantities from centroid to
444    vertices for each volume using
445    second order scheme.
446    """
447       
448    a, b = quantity.compute_gradients()
449
450    X = quantity.domain.get_vertex_coordinates()
451    qc = quantity.centroid_values
452    qv = quantity.vertex_values
453   
454    #Check each triangle
455    for k in range(quantity.domain.number_of_elements):
456        #Centroid coordinates           
457        x, y = quantity.domain.centroids[k]
458       
459        #vertex coordinates
460        x0, y0, x1, y1, x2, y2 = X[k,:]
461       
462        #Extrapolate
463        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
464        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
465        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
466
467
468def compute_gradients(quantity):                   
469    """Compute gradients of triangle surfaces defined by centroids of
470    neighbouring volumes.
471    If one edge is on the boundary, use own centroid as neighbour centroid.
472    If two or more are on the boundary, fall back to first order scheme.
473    """
474
475    from Numeric import zeros, Float
476    from util import gradient
477   
478    centroids = quantity.domain.centroids
479    surrogate_neighbours = quantity.domain.surrogate_neighbours   
480    centroid_values = quantity.centroid_values   
481    number_of_boundaries = quantity.domain.number_of_boundaries     
482   
483    N = centroid_values.shape[0]
484
485    a = zeros(N, Float)
486    b = zeros(N, Float)
487   
488    for k in range(N):
489        if number_of_boundaries[k] < 2:
490            #Two or three true neighbours
491
492            #Get indices of neighbours (or self when used as surrogate)     
493            k0, k1, k2 = surrogate_neighbours[k,:]
494
495            #Get data       
496            q0 = centroid_values[k0]
497            q1 = centroid_values[k1]
498            q2 = centroid_values[k2]                   
499
500            x0, y0 = centroids[k0] #V0 centroid
501            x1, y1 = centroids[k1] #V1 centroid
502            x2, y2 = centroids[k2] #V2 centroid
503
504            #Gradient
505            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
506       
507        elif number_of_boundaries[k] == 2:
508            #One true neighbour
509
510            #Get index of the one neighbour
511            for k0 in surrogate_neighbours[k,:]:
512                if k0 != k: break
513            assert k0 != k
514
515            k1 = k  #self
516
517            #Get data
518            q0 = centroid_values[k0]
519            q1 = centroid_values[k1]
520
521            x0, y0 = centroids[k0] #V0 centroid
522            x1, y1 = centroids[k1] #V1 centroid       
523
524            #Gradient
525            det = x0*y1 - x1*y0
526            if det != 0.0:
527                a[k] = (y1*q0 - y0*q1)/det
528                b[k] = (x0*q1 - x1*q0)/det
529
530        else:
531            #No true neighbours -       
532            #Fall back to first order scheme
533            pass
534       
535   
536    return a, b
537       
538                   
539
540def limit(quantity):       
541    """Limit slopes for each volume to eliminate artificial variance
542    introduced by e.g. second order extrapolator
543   
544    This is an unsophisticated limiter as it does not take into
545    account dependencies among quantities.
546   
547    precondition:
548    vertex values are estimated from gradient
549    postcondition:
550    vertex values are updated
551    """
552
553    from Numeric import zeros, Float
554
555    N = quantity.domain.number_of_elements
556   
557    beta = quantity.domain.beta
558       
559    qc = quantity.centroid_values
560    qv = quantity.vertex_values
561       
562    #Find min and max of this and neighbour's centroid values
563    qmax = zeros(qc.shape, Float)
564    qmin = zeros(qc.shape, Float)       
565   
566    for k in range(N):
567        qmax[k] = qmin[k] = qc[k]
568        for i in range(3):
569            n = quantity.domain.neighbours[k,i]
570            if n >= 0:
571                qn = qc[n] #Neighbour's centroid value
572               
573                qmin[k] = min(qmin[k], qn)
574                qmax[k] = max(qmax[k], qn)
575     
576   
577    #Diffences between centroids and maxima/minima
578    dqmax = qmax - qc
579    dqmin = qmin - qc
580       
581    #Deltas between vertex and centroid values
582    dq = zeros(qv.shape, Float)
583    for i in range(3):
584        dq[:,i] = qv[:,i] - qc
585
586    #Phi limiter   
587    for k in range(N):
588       
589        #Find the gradient limiter (phi) across vertices
590        phi = 1.0
591        for i in range(3):
592            r = 1.0
593            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
594            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
595           
596            phi = min( min(r*beta, 1), phi )   
597
598        #Then update using phi limiter
599        for i in range(3):           
600            qv[k,i] = qc[k] + phi*dq[k,i]
601
602
603
604import compile
605if compile.can_use_C_extension('quantity_ext.c'):
606    #Replace python version with c implementations
607       
608    from quantity_ext import limit, compute_gradients,\
609    extrapolate_second_order, interpolate_from_vertices_to_edges, update
610
Note: See TracBrowser for help on using the repository browser.