source: storm_surge/pyvolution/quantity.py @ 1

Last change on this file since 1 was 1, checked in by duncan, 20 years ago

initial import

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