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

Last change on this file since 474 was 461, checked in by duncan, 21 years ago

quantity.set_values update

File size: 22.1 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', indexes = None):
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, indexes = indexes)
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.centroid_coordinates
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', indexes = None):
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        indexes - if this action is carried out on a subset of elements
165        The element indexes are specified here.
166       
167        In case of location == 'centroid' the dimension values must
168        be a list of a Numerical array of length N, N being the number
169        of elements in the mesh. Otherwise it must be of dimension Nx3
170
171        The values will be stored in elements following their
172        internal ordering.
173
174        If selected location is vertices, values for centroid and edges
175        will be assigned interpolated values.
176        In any other case, only values for the specified locations
177        will be assigned and the others will be left undefined.
178        """
179
180        from Numeric import array, Float, Int
181
182        values = array(values).astype(Float)
183
184        if (indexes <> None):
185            indexes = array(indexes).astype(Int)
186            msg = 'Number of values must match number of indexes'
187            assert values.shape[0] == indexes.shape[0], msg
188           
189        N = self.centroid_values.shape[0]
190       
191        if location == 'centroids':
192            assert len(values.shape) == 1, 'Values array must be 1d'
193
194            if indexes == None:
195                msg = 'Number of values must match number of elements'
196                assert values.shape[0] == N, msg
197           
198                self.centroid_values = values
199            else:
200                msg = 'Number of values must match number of indexes'
201                assert values.shape[0] == indexes.shape[0], msg
202               
203                #Brute force
204                for i in range(len(indexes)):
205                    self.centroid_values[indexes[i]] = values[i]
206                   
207        elif location == 'edges':
208            assert len(values.shape) == 2, 'Values array must be 2d'
209
210            msg = 'Number of values must match number of elements'
211            assert values.shape[0] == N, msg
212           
213            msg = 'Array must be N x 3'           
214            assert values.shape[1] == 3, msg
215           
216            self.edge_values = values
217        else:
218            if len(values.shape) == 1:
219
220                if indexes == None:
221                    #Values are being specified once for each unique vertex
222                    msg = 'Number of values must match number of vertices'
223                    assert values.shape[0] == self.domain.coordinates.shape[0], msg
224                    self.set_vertex_values(values)
225                else:
226                    for element_index, value in map(None, indexes, values): 
227                        self.vertex_values[element_index, 0] = value
228                        self.vertex_values[element_index, 1] = value
229                        self.vertex_values[element_index, 2] = value
230            elif len(values.shape) == 2:
231                #Vertex values are given as a triplet for each triangle
232               
233                msg = 'Array must be N x 3'           
234                assert values.shape[1] == 3, msg
235               
236                if indexes == None:
237                    self.vertex_values = values
238                else:
239                    for element_index, value in map(None, indexes, values): 
240                        self.vertex_values[element_index] = value
241            else:   
242                msg = 'Values array must be 1d or 2d'
243                raise msg
244           
245
246    # FIXME have a get_vertex_values as well, so the 'level' quantity can be
247    # set, based on the elevation   
248    def set_vertex_values(self, A):
249        """Set vertex values for all triangles based on input array A
250        which is assumed to have one entry per (unique) vertex, i.e.
251        one value for each row in array self.domain.coordinates.
252        """
253
254        from Numeric import array, Float
255
256        #Assert that A can be converted to a Numeric array of appropriate dim
257        A = array(A, Float)
258
259        assert len(A.shape) == 1
260        assert A.shape[0] == self.domain.coordinates.shape[0]
261
262        N = A.shape[0]
263       
264        #Go through list of unique vertices
265        for k in range(N):
266            L = self.domain.vertexlist[k]
267               
268            if L is None: continue #In case there are unused points
269
270            #Go through all triangle, vertex pairs
271            #touching vertex k and set corresponding vertex value
272            for triangle_id, vertex_id in L:
273                self.vertex_values[triangle_id, vertex_id] = A[k]
274               
275        #Intialise centroid and edge_values
276        self.interpolate()
277
278 
279    def smooth_vertex_values(self, value_array='field_values',
280                             precision = None):
281        """ Smooths field_values or conserved_quantities data.
282        TODO: be able to smooth individual fields
283        NOTE:  This function does not have a test.
284        FIXME: NOT DONE - do we need it?
285        """
286
287        from Numeric import concatenate, zeros, Float, Int, array, reshape
288
289       
290        A,V = self.get_vertex_values(xy=False,
291                                     value_array=value_array,
292                                     smooth = True,
293                                     precision = precision)
294
295        #Set some field values
296        for volume in self:
297            for i,v in enumerate(volume.vertices):
298                if value_array == 'field_values':
299                    volume.set_field_values('vertex', i, A[v,:])
300                elif value_array == 'conserved_quantities':
301                    volume.set_conserved_quantities('vertex', i, A[v,:])
302
303        if value_array == 'field_values':
304            self.precompute()
305        elif value_array == 'conserved_quantities':
306            Volume.interpolate_conserved_quantities()
307       
308   
309    #Method for outputting model results
310    #FIXME: Split up into geometric and numeric stuff.
311    #FIXME: Geometric (X,Y,V) should live in mesh.py
312    #FIXME: STill remember to move XY to mesh
313    def get_vertex_values(self,
314                          xy=True, 
315                          smooth = None,
316                          precision = None,
317                          reduction = None):
318        """Return vertex values like an OBJ format
319
320        The vertex values are returned as one sequence in the 1D float array A.
321        If requested the coordinates will be returned in 1D arrays X and Y.
322       
323        The connectivity is represented as an integer array, V, of dimension
324        M x 3, where M is the number of volumes. Each row has three indices
325        into the X, Y, A arrays defining the triangle.
326
327        if smooth is True, vertex values corresponding to one common
328        coordinate set will be smoothed according to the given
329        reduction operator. In this case vertex coordinates will be
330        de-duplicated.
331       
332        If no smoothings is required, vertex coordinates and values will
333        be aggregated as a concatenation of values at
334        vertices 0, vertices 1 and vertices 2
335
336       
337        Calling convention
338        if xy is True:
339           X,Y,A,V = get_vertex_values
340        else:
341           A,V = get_vertex_values         
342           
343        """
344
345        from Numeric import concatenate, zeros, Float, Int, array, reshape
346
347
348        if smooth is None:
349            smooth = self.domain.smooth
350
351        if precision is None:
352            precision = Float
353
354        if reduction is None:
355            reduction = self.domain.reduction
356           
357        #Create connectivity
358        V = self.domain.get_triangles(unique=smooth)
359                       
360        if smooth == True:
361
362            N = len(self.domain.vertexlist)
363            A = zeros(N, precision)
364
365            #Smoothing loop
366            for k in range(N):
367                L = self.domain.vertexlist[k]
368               
369                #Go through all triangle, vertex pairs
370                #contributing to vertex k and register vertex value
371
372                if L is None: continue #In case there are unused points
373               
374                contributions = []
375                for volume_id, vertex_id in L:
376                    v = self.vertex_values[volume_id, vertex_id]
377                    contributions.append(v)
378
379                A[k] = reduction(contributions)   
380
381
382            if xy is True:
383                X = self.domain.coordinates[:,0].astype(precision)
384                Y = self.domain.coordinates[:,1].astype(precision)
385               
386                return X, Y, A, V
387            else:
388                return A, V
389        else:
390            #Don't smooth
391
392            A = self.vertex_values.flat
393
394            #Do vertex coordinates   
395            if xy is True:               
396                C = self.domain.get_vertex_coordinates()
397
398                X = C[:,0:6:2].copy()
399                Y = C[:,1:6:2].copy()               
400
401                return X.flat, Y.flat, A, V           
402            else:
403                return A, V
404
405           
406           
407
408
409class Conserved_quantity(Quantity):
410    """Class conserved quantity adds to Quantity:
411
412    boundary values, storage and method for updating, and
413    methods for extrapolation from centropid to vertices inluding
414    gradients and limiters
415    """
416
417    def __init__(self, domain, vertex_values=None):
418        Quantity.__init__(self, domain, vertex_values)
419       
420        from Numeric import zeros, Float
421
422        #Allocate space for boundary values
423        L = len(domain.boundary)
424        self.boundary_values = zeros(L, Float)
425
426        #Allocate space for updates of conserved quantities by
427        #flux calculations and forcing functions
428
429        N = domain.number_of_elements
430        self.explicit_update = zeros(N, Float )
431        self.semi_implicit_update = zeros(N, Float )
432               
433
434    def update(self, timestep):
435        #Call correct module function
436        #(either from this module or C-extension)
437        return update(self, timestep)
438
439
440    def compute_gradients(self):
441        #Call correct module function
442        #(either from this module or C-extension)
443        return compute_gradients(self)
444           
445
446    def limit(self):
447        #Call correct module function
448        #(either from this module or C-extension)
449        limit(self)
450       
451       
452    def extrapolate_first_order(self):
453        """Extrapolate conserved quantities from centroid to
454        vertices for each volume using
455        first order scheme.
456        """
457       
458        qc = self.centroid_values
459        qv = self.vertex_values
460
461        for i in range(3):
462            qv[:,i] = qc
463           
464           
465    def extrapolate_second_order(self):
466        #Call correct module function
467        #(either from this module or C-extension)
468        extrapolate_second_order(self)
469
470
471def update(quantity, timestep):   
472    """Update centroid values based on values stored in
473    explicit_update and semi_implicit_update as well as given timestep
474    """
475   
476    from Numeric import sum, equal, ones, Float
477       
478    N = quantity.centroid_values.shape[0]
479
480
481    #Divide by semi_implicit update by conserved quantity
482    for k in range(N):
483        x = quantity.centroid_values[k] 
484        if x == 0.0:
485            quantity.semi_implicit_update[k] = 0.0           
486        else:
487            quantity.semi_implicit_update[k] /= x             
488           
489
490       
491    #Explicit updates
492    quantity.centroid_values += timestep*quantity.explicit_update
493           
494    #Semi implicit updates
495    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
496
497    if sum(equal(denominator, 0.0)) > 0.0:
498        msg = 'Zero division in semi implicit update. Call Stephen :-)'
499        raise msg
500    else:
501        #Update conserved_quantities from semi implicit updates
502        quantity.centroid_values /= denominator
503
504
505def interpolate_from_vertices_to_edges(quantity):
506    """Compute edge values from vertex values using linear interpolation
507    """
508
509    for k in range(quantity.vertex_values.shape[0]):
510        q0 = quantity.vertex_values[k, 0]
511        q1 = quantity.vertex_values[k, 1]
512        q2 = quantity.vertex_values[k, 2]
513           
514        quantity.edge_values[k, 0] = 0.5*(q1+q2)
515        quantity.edge_values[k, 1] = 0.5*(q0+q2) 
516        quantity.edge_values[k, 2] = 0.5*(q0+q1)
517
518
519
520def extrapolate_second_order(quantity):       
521    """Extrapolate conserved quantities from centroid to
522    vertices for each volume using
523    second order scheme.
524    """
525       
526    a, b = quantity.compute_gradients()
527
528    X = quantity.domain.get_vertex_coordinates()
529    qc = quantity.centroid_values
530    qv = quantity.vertex_values
531   
532    #Check each triangle
533    for k in range(quantity.domain.number_of_elements):
534        #Centroid coordinates           
535        x, y = quantity.domain.centroid_coordinates[k]
536       
537        #vertex coordinates
538        x0, y0, x1, y1, x2, y2 = X[k,:]
539       
540        #Extrapolate
541        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
542        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
543        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)           
544
545
546def compute_gradients(quantity):                   
547    """Compute gradients of triangle surfaces defined by centroids of
548    neighbouring volumes.
549    If one edge is on the boundary, use own centroid as neighbour centroid.
550    If two or more are on the boundary, fall back to first order scheme.
551    """
552
553    from Numeric import zeros, Float
554    from util import gradient
555   
556    centroid_coordinates = quantity.domain.centroid_coordinates
557    surrogate_neighbours = quantity.domain.surrogate_neighbours   
558    centroid_values = quantity.centroid_values   
559    number_of_boundaries = quantity.domain.number_of_boundaries     
560   
561    N = centroid_values.shape[0]
562
563    a = zeros(N, Float)
564    b = zeros(N, Float)
565   
566    for k in range(N):
567        if number_of_boundaries[k] < 2:
568            #Two or three true neighbours
569
570            #Get indices of neighbours (or self when used as surrogate)     
571            k0, k1, k2 = surrogate_neighbours[k,:]
572
573            #Get data       
574            q0 = centroid_values[k0]
575            q1 = centroid_values[k1]
576            q2 = centroid_values[k2]                   
577
578            x0, y0 = centroid_coordinates[k0] #V0 centroid
579            x1, y1 = centroid_coordinates[k1] #V1 centroid
580            x2, y2 = centroid_coordinates[k2] #V2 centroid
581
582            #Gradient
583            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
584       
585        elif number_of_boundaries[k] == 2:
586            #One true neighbour
587
588            #Get index of the one neighbour
589            for k0 in surrogate_neighbours[k,:]:
590                if k0 != k: break
591            assert k0 != k
592
593            k1 = k  #self
594
595            #Get data
596            q0 = centroid_values[k0]
597            q1 = centroid_values[k1]
598
599            x0, y0 = centroid_coordinates[k0] #V0 centroid
600            x1, y1 = centroid_coordinates[k1] #V1 centroid       
601
602            #Gradient
603            det = x0*y1 - x1*y0
604            if det != 0.0:
605                a[k] = (y1*q0 - y0*q1)/det
606                b[k] = (x0*q1 - x1*q0)/det
607
608        else:
609            #No true neighbours -       
610            #Fall back to first order scheme
611            pass
612       
613   
614    return a, b
615       
616                   
617
618def limit(quantity):       
619    """Limit slopes for each volume to eliminate artificial variance
620    introduced by e.g. second order extrapolator
621   
622    This is an unsophisticated limiter as it does not take into
623    account dependencies among quantities.
624   
625    precondition:
626    vertex values are estimated from gradient
627    postcondition:
628    vertex values are updated
629    """
630
631    from Numeric import zeros, Float
632
633    N = quantity.domain.number_of_elements
634   
635    beta = quantity.domain.beta
636       
637    qc = quantity.centroid_values
638    qv = quantity.vertex_values
639       
640    #Find min and max of this and neighbour's centroid values
641    qmax = zeros(qc.shape, Float)
642    qmin = zeros(qc.shape, Float)       
643   
644    for k in range(N):
645        qmax[k] = qmin[k] = qc[k]
646        for i in range(3):
647            n = quantity.domain.neighbours[k,i]
648            if n >= 0:
649                qn = qc[n] #Neighbour's centroid value
650               
651                qmin[k] = min(qmin[k], qn)
652                qmax[k] = max(qmax[k], qn)
653     
654   
655    #Diffences between centroids and maxima/minima
656    dqmax = qmax - qc
657    dqmin = qmin - qc
658       
659    #Deltas between vertex and centroid values
660    dq = zeros(qv.shape, Float)
661    for i in range(3):
662        dq[:,i] = qv[:,i] - qc
663
664    #Phi limiter   
665    for k in range(N):
666       
667        #Find the gradient limiter (phi) across vertices
668        phi = 1.0
669        for i in range(3):
670            r = 1.0
671            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
672            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
673           
674            phi = min( min(r*beta, 1), phi )   
675
676        #Then update using phi limiter
677        for i in range(3):           
678            qv[k,i] = qc[k] + phi*dq[k,i]
679
680
681
682import compile
683if compile.can_use_C_extension('quantity_ext.c'):
684    #Replace python version with c implementations
685       
686    from quantity_ext import limit, compute_gradients,\
687    extrapolate_second_order, interpolate_from_vertices_to_edges, update
688
Note: See TracBrowser for help on using the repository browser.