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

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

Cleaned up in get_vertex_values

File size: 18.6 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 - do we need it?
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    #FIXME: Split up into geometric and numeric stuff.
237    #FIXME: Geometric (X,Y,V) should live in mesh.py
238    def get_vertex_values(self,
239                          xy=True, 
240                          smooth = None,
241                          precision = None,
242                          reduction = None):
243        """Return vertex values like an OBJ format
244
245        The vertex values are returned as one sequence in the 1D float array A.
246        If requested the coordinates will be returned in 1D arrays X and Y.
247       
248        The connectivity is represented as an integer array, V, of dimension
249        M x 3, where M is the number of volumes. Each row has three indices
250        into the X, Y, A arrays defining the triangle.
251
252        if smooth is True, vertex values corresponding to one common
253        coordinate set will be smoothed according to the given
254        reduction operator. In this case vertex coordinates will be
255        de-duplicated.
256       
257        If no smoothings is required, vertex coordinates and values will
258        be aggregated as a concatenation of of values at
259        vertices 0, vertices 1 and vertices 2
260
261       
262        Calling convention
263        if xy is True:
264           X,Y,A,V = get_vertex_values
265        else:
266           A,V = get_vertex_values         
267           
268        """
269
270        from Numeric import concatenate, zeros, Float, Int, array, reshape
271
272
273        if smooth is None:
274            smooth = self.domain.smooth
275
276        if precision is None:
277            precision = Float
278
279        if reduction is None:
280            reduction = self.domain.reduction
281           
282        #Create connectivity
283        V = self.domain.get_vertices(unique=smooth)
284                       
285        if smooth == True:
286
287            N = len(self.domain.vertexlist)
288            A = zeros(N, precision)
289
290            #Smoothing loop
291            for k in range(N):
292                L = self.domain.vertexlist[k]
293               
294                #Go through all triangle, vertex pairs
295                #contributing to vertex k and register vertex value
296                contributions = []               
297                for volume_id, vertex_id in L:
298
299                    v = self.vertex_values[volume_id, vertex_id]
300                    contributions.append(v)
301
302                A[k] = reduction(contributions)   
303
304
305            if xy is True:
306                X = self.domain.coordinates[:,0].astype(precision)
307                Y = self.domain.coordinates[:,1].astype(precision)
308               
309                return X, Y, A, V
310            else:
311                return A, V
312        else:
313            #Don't smooth
314
315            A = self.vertex_values.flat
316
317            #Do vertex coordinates   
318            if xy is True:               
319                C = self.domain.get_vertex_coordinates()
320
321                X = C[:,0:6:2].copy()
322                Y = C[:,1:6:2].copy()               
323
324                return X.flat, Y.flat, A, V           
325            else:
326                return A, V
327
328           
329           
330
331
332class Conserved_quantity(Quantity):
333    """Class conserved quantity adds to Quantity:
334
335    boundary values, storage and method for updating, and
336    methods for extrapolation from centropid to vertices inluding
337    gradients and limiters
338    """
339
340    def __init__(self, domain, vertex_values=None):
341        Quantity.__init__(self, domain, vertex_values)
342       
343        from Numeric import zeros, Float
344
345        #Allocate space for boundary values
346        L = len(domain.boundary)
347        self.boundary_values = zeros(L, Float)
348
349        #Allocate space for updates of conserved quantities by
350        #flux calculations and forcing functions
351
352        N = domain.number_of_elements
353        self.explicit_update = zeros(N, Float )
354        self.semi_implicit_update = zeros(N, Float )
355               
356
357    def update(self, timestep):
358        #Call correct module function
359        #(either from this module or C-extension)
360        return update(self, timestep)
361
362
363    def compute_gradients(self):
364        #Call correct module function
365        #(either from this module or C-extension)
366        return compute_gradients(self)
367           
368
369    def limit(self):
370        #Call correct module function
371        #(either from this module or C-extension)
372        limit(self)
373       
374       
375    def extrapolate_first_order(self):
376        """Extrapolate conserved quantities from centroid to
377        vertices for each volume using
378        first order scheme.
379        """
380       
381        qc = self.centroid_values
382        qv = self.vertex_values
383
384        for i in range(3):
385            qv[:,i] = qc
386           
387           
388    def extrapolate_second_order(self):
389        #Call correct module function
390        #(either from this module or C-extension)
391        extrapolate_second_order(self)
392
393
394def update(quantity, timestep):   
395    """Update centroid values based on values stored in
396    explicit_update and semi_implicit_update as well as given timestep
397    """
398   
399    from Numeric import sum, equal, ones, Float
400       
401    N = quantity.centroid_values.shape[0]
402       
403    #Explicit updates
404    quantity.centroid_values += timestep*quantity.explicit_update
405           
406    #Semi implicit updates
407    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
408
409    if sum(equal(denominator, 0.0)) > 0.0:
410        msg = 'Zero division in semi implicit update. Call Stephen :-)'
411        raise msg
412    else:
413        #Update conserved_quantities from semi implicit updates
414        quantity.centroid_values /= denominator
415
416
417def interpolate_from_vertices_to_edges(quantity):
418    """Compute edge values from vertex values using linear interpolation
419    """
420
421    for k in range(quantity.vertex_values.shape[0]):
422        q0 = quantity.vertex_values[k, 0]
423        q1 = quantity.vertex_values[k, 1]
424        q2 = quantity.vertex_values[k, 2]
425           
426        quantity.edge_values[k, 0] = 0.5*(q1+q2)
427        quantity.edge_values[k, 1] = 0.5*(q0+q2) 
428        quantity.edge_values[k, 2] = 0.5*(q0+q1)
429
430
431
432def extrapolate_second_order(quantity):       
433    """Extrapolate conserved quantities from centroid to
434    vertices for each volume using
435    second order scheme.
436    """
437       
438    a, b = quantity.compute_gradients()
439
440    X = quantity.domain.get_vertex_coordinates()
441    qc = quantity.centroid_values
442    qv = quantity.vertex_values
443   
444    #Check each triangle
445    for k in range(quantity.domain.number_of_elements):
446        #Centroid coordinates           
447        x, y = quantity.domain.centroids[k]
448       
449        #vertex coordinates
450        x0, y0, x1, y1, x2, y2 = X[k,:]
451       
452        #Extrapolate
453        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
454        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
455        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
456
457
458def compute_gradients(quantity):                   
459    """Compute gradients of triangle surfaces defined by centroids of
460    neighbouring volumes.
461    If one edge is on the boundary, use own centroid as neighbour centroid.
462    If two or more are on the boundary, fall back to first order scheme.
463    """
464
465    from Numeric import zeros, Float
466    from util import gradient
467   
468    centroids = quantity.domain.centroids
469    surrogate_neighbours = quantity.domain.surrogate_neighbours   
470    centroid_values = quantity.centroid_values   
471    number_of_boundaries = quantity.domain.number_of_boundaries     
472   
473    N = centroid_values.shape[0]
474
475    a = zeros(N, Float)
476    b = zeros(N, Float)
477   
478    for k in range(N):
479        if number_of_boundaries[k] < 2:
480            #Two or three true neighbours
481
482            #Get indices of neighbours (or self when used as surrogate)     
483            k0, k1, k2 = surrogate_neighbours[k,:]
484
485            #Get data       
486            q0 = centroid_values[k0]
487            q1 = centroid_values[k1]
488            q2 = centroid_values[k2]                   
489
490            x0, y0 = centroids[k0] #V0 centroid
491            x1, y1 = centroids[k1] #V1 centroid
492            x2, y2 = centroids[k2] #V2 centroid
493
494            #Gradient
495            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
496       
497        elif number_of_boundaries[k] == 2:
498            #One true neighbour
499
500            #Get index of the one neighbour
501            for k0 in surrogate_neighbours[k,:]:
502                if k0 != k: break
503            assert k0 != k
504
505            k1 = k  #self
506
507            #Get data
508            q0 = centroid_values[k0]
509            q1 = centroid_values[k1]
510
511            x0, y0 = centroids[k0] #V0 centroid
512            x1, y1 = centroids[k1] #V1 centroid       
513
514            #Gradient
515            det = x0*y1 - x1*y0
516            if det != 0.0:
517                a[k] = (y1*q0 - y0*q1)/det
518                b[k] = (x0*q1 - x1*q0)/det
519
520        else:
521            #No true neighbours -       
522            #Fall back to first order scheme
523            pass
524       
525   
526    return a, b
527       
528                   
529
530def limit(quantity):       
531    """Limit slopes for each volume to eliminate artificial variance
532    introduced by e.g. second order extrapolator
533   
534    This is an unsophisticated limiter as it does not take into
535    account dependencies among quantities.
536   
537    precondition:
538    vertex values are estimated from gradient
539    postcondition:
540    vertex values are updated
541    """
542
543    from Numeric import zeros, Float
544
545    N = quantity.domain.number_of_elements
546   
547    beta = quantity.domain.beta
548       
549    qc = quantity.centroid_values
550    qv = quantity.vertex_values
551       
552    #Find min and max of this and neighbour's centroid values
553    qmax = zeros(qc.shape, Float)
554    qmin = zeros(qc.shape, Float)       
555   
556    for k in range(N):
557        qmax[k] = qmin[k] = qc[k]
558        for i in range(3):
559            n = quantity.domain.neighbours[k,i]
560            if n >= 0:
561                qn = qc[n] #Neighbour's centroid value
562               
563                qmin[k] = min(qmin[k], qn)
564                qmax[k] = max(qmax[k], qn)
565     
566   
567    #Diffences between centroids and maxima/minima
568    dqmax = qmax - qc
569    dqmin = qmin - qc
570       
571    #Deltas between vertex and centroid values
572    dq = zeros(qv.shape, Float)
573    for i in range(3):
574        dq[:,i] = qv[:,i] - qc
575
576    #Phi limiter   
577    for k in range(N):
578       
579        #Find the gradient limiter (phi) across vertices
580        phi = 1.0
581        for i in range(3):
582            r = 1.0
583            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
584            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
585           
586            phi = min( min(r*beta, 1), phi )   
587
588        #Then update using phi limiter
589        for i in range(3):           
590            qv[k,i] = qc[k] + phi*dq[k,i]
591
592
593
594import compile
595if compile.can_use_C_extension('quantity_ext.c'):
596    #Replace python version with c implementations
597       
598    from quantity_ext import limit, compute_gradients,\
599    extrapolate_second_order, interpolate_from_vertices_to_edges, update
600
Note: See TracBrowser for help on using the repository browser.