source: development/pyvolution-1d/quantity.py @ 3365

Last change on this file since 3365 was 3362, checked in by jakeman, 19 years ago

New files dam2.py and dry_dam.py further test numerical solution
against analytical solution of
Stoker 57. Limiting is still not working correctly.

File size: 22.1 KB
Line 
1"""Class Quantity - Implements values at each 1d element
2
3To create:
4
5   Quantity(domain, vertex_values)
6
7   domain: Associated domain structure. Required.
8
9   vertex_values: N x 2 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 domain import Domain
23        from Numeric import array, zeros, Float
24
25        msg = 'First argument in Quantity.__init__ '
26        msg += 'must be of class Domain (or a subclass thereof)'
27        assert isinstance(domain, Domain), msg
28
29        if vertex_values is None:
30            N = domain.number_of_elements
31            self.vertex_values = zeros((N, 2), Float)
32        else:
33            self.vertex_values = array(vertex_values, Float)
34
35            N, V = self.vertex_values.shape
36            assert V == 2,\
37                   'Two 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, 2), Float)
52        #edge values are values of the ends of each interval
53       
54        #does oe dimension need edge values???
55
56        #Intialise centroid values
57        self.interpolate()
58
59    #Methods for operator overloading
60    def __len__(self):
61        return self.centroid_values.shape[0]
62   
63    def interpolate(self):
64        """Compute interpolated values at centroid
65        Pre-condition: vertex_values have been set
66        """
67
68        N = self.vertex_values.shape[0]
69        for i in range(N):
70            v0 = self.vertex_values[i, 0]
71            v1 = self.vertex_values[i, 1]
72
73            self.centroid_values[i] = (v0 + v1)/2
74
75    def set_values(self, X, location='vertices'):
76        """Set values for quantity
77
78        X: Compatible list, Numeric array (see below), constant or function
79        location: Where values are to be stored.
80                  Permissible options are: vertices, centroid
81                  Default is "vertices"
82
83        In case of location == 'centroid' the dimension values must
84        be a list of a Numerical array of length N, N being the number
85        of elements in the mesh. Otherwise it must be of dimension Nx3
86
87        The values will be stored in elements following their
88        internal ordering.
89
90        If values are described a function, it will be evaluated at specified points
91
92        If selected location is vertices, values for centroid and edges
93        will be assigned interpolated values.
94        In any other case, only values for the specified locations
95        will be assigned and the others will be left undefined.
96        """
97
98        if location not in ['vertices', 'centroids']:#, 'edges']:
99            msg = 'Invalid location: %s, (possible choices vertices, centroids)' %location
100            raise msg
101
102        if X is None:
103            msg = 'Given values are None'
104            raise msg
105
106        import types
107
108        if callable(X):
109            #Use function specific method
110            self.set_function_values(X, location)
111        elif type(X) in [types.FloatType, types.IntType, types.LongType]:
112            if location == 'centroids':
113                self.centroid_values[:] = X
114            else:
115                self.vertex_values[:] = X
116
117        else:
118            #Use array specific method
119            self.set_array_values(X, location)
120
121        if location == 'vertices':
122            #Intialise centroid and edge values
123            self.interpolate()
124
125
126
127
128    def set_function_values(self, f, location='vertices'):
129        """Set values for quantity using specified function
130
131        f: x -> z Function where x and z are arrays
132        location: Where values are to be stored.
133                  Permissible options are: vertices, centroid
134                  Default is "vertices"
135        """
136       
137        if location == 'centroids':
138           
139            P = self.domain.centroids
140            self.set_values(f(P), location)
141        else:
142            #Vertices
143            P = self.domain.get_vertices()
144           
145            for i in range(2):
146                self.vertex_values[:,i] = f(P[:,i])       
147
148    def set_array_values(self, values, location='vertices'):
149        """Set values for quantity
150
151        values: Numeric array
152        location: Where values are to be stored.
153                  Permissible options are: vertices, centroid, edges
154                  Default is "vertices"
155
156        In case of location == 'centroid' the dimension values must
157        be a list of a Numerical array of length N, N being the number
158        of elements in the mesh. Otherwise it must be of dimension Nx2
159
160        The values will be stored in elements following their
161        internal ordering.
162
163        If selected location is vertices, values for centroid
164        will be assigned interpolated values.
165        In any other case, only values for the specified locations
166        will be assigned and the others will be left undefined.
167        """
168
169        from Numeric import array, Float
170
171        values = array(values).astype(Float)
172
173        N = self.centroid_values.shape[0]
174
175        msg = 'Number of values must match number of elements'
176        assert values.shape[0] == N, msg
177
178        if location == 'centroids':
179            assert len(values.shape) == 1, 'Values array must be 1d'
180            self.centroid_values = values
181        #elif location == 'edges':
182        #    assert len(values.shape) == 2, 'Values array must be 2d'
183        #    msg = 'Array must be N x 2'
184        #    self.edge_values = values
185        else:
186            assert len(values.shape) == 2, 'Values array must be 2d'
187            msg = 'Array must be N x 2'
188            assert values.shape[1] == 2, msg
189
190            self.vertex_values = values
191
192
193    def get_values(self, location='vertices', indices = None):
194        """get values for quantity
195
196        return X, Compatible list, Numeric array (see below)
197        location: Where values are to be stored.
198                  Permissible options are: vertices, edges, centroid
199                  and unique vertices. Default is 'vertices'
200
201        In case of location == 'centroids' the dimension values must
202        be a list of a Numerical array of length N, N being the number
203        of elements. Otherwise it must be of dimension Nx3
204
205        The returned values with be a list the length of indices
206        (N if indices = None).  Each value will be a list of the three
207        vertex values for this quantity.
208
209        Indices is the set of element ids that the operation applies to.
210
211        """
212        from Numeric import take
213
214        #if location not in ['vertices', 'centroids', 'edges', 'unique vertices']:
215        if location not in ['vertices', 'centroids', 'unique vertices']:
216            msg = 'Invalid location: %s' %location
217            raise msg
218
219        import types, Numeric
220        assert type(indices) in [types.ListType, types.NoneType,
221                                 Numeric.ArrayType],\
222                                 'Indices must be a list or None'
223
224        if location == 'centroids':
225            if (indices ==  None):
226                indices = range(len(self))
227            return take(self.centroid_values,indices)
228        #elif location == 'edges':
229        #    if (indices ==  None):
230        #        indices = range(len(self))
231        #    return take(self.edge_values,indices)
232        elif location == 'unique vertices':
233            if (indices ==  None):
234                indices=range(self.domain.coordinates.shape[0])
235            vert_values = []
236            #Go through list of unique vertices
237            for unique_vert_id in indices:
238                triangles = self.domain.vertexlist[unique_vert_id]
239
240                #In case there are unused points
241                if triangles is None:
242                    msg = 'Unique vertex not associated with triangles'
243                    raise msg
244
245                # Go through all triangle, vertex pairs
246                # Average the values
247                sum = 0
248                for triangle_id, vertex_id in triangles:
249                    sum += self.vertex_values[triangle_id, vertex_id]
250                vert_values.append(sum/len(triangles))
251            return Numeric.array(vert_values)
252        else:
253            if (indices ==  None):
254                indices = range(len(self))
255            return take(self.vertex_values,indices)
256
257    #Method for outputting model results
258    #FIXME: Split up into geometric and numeric stuff.
259    #FIXME: Geometric (X,Y,V) should live in mesh.py
260    #FIXME: STill remember to move XY to mesh
261    def get_vertex_values(self,
262                          #xy=True,
263                          x=True,
264                          smooth = None,
265                          precision = None,
266                          reduction = None):
267        """Return vertex values like an OBJ format
268
269        The vertex values are returned as one sequence in the 1D float array A.
270        If requested the coordinates will be returned in 1D arrays X and Y.
271
272        The connectivity is represented as an integer array, V, of dimension
273        M x 3, where M is the number of volumes. Each row has three indices
274        into the X, Y, A arrays defining the triangle.
275
276        if smooth is True, vertex values corresponding to one common
277        coordinate set will be smoothed according to the given
278        reduction operator. In this case vertex coordinates will be
279        de-duplicated.
280
281        If no smoothings is required, vertex coordinates and values will
282        be aggregated as a concatenation of values at
283        vertices 0, vertices 1 and vertices 2
284
285
286        Calling convention
287        if xy is True:
288           X,Y,A,V = get_vertex_values
289        else:
290           A,V = get_vertex_values
291
292        """
293
294        from Numeric import concatenate, zeros, Float, Int, array, reshape
295
296
297        if smooth is None:
298            smooth = self.domain.smooth
299
300        if precision is None:
301            precision = Float
302
303        if reduction is None:
304            reduction = self.domain.reduction
305
306        #Create connectivity
307
308        if smooth == True:
309
310            V = self.domain.get_vertices()
311            N = len(self.domain.vertexlist)
312            #N = len(self.domain.vertices)
313            A = zeros(N, precision)
314
315            #Smoothing loop
316            for k in range(N):
317                L = self.domain.vertexlist[k]
318                #L = self.domain.vertices[k]
319
320                #Go through all triangle, vertex pairs
321                #contributing to vertex k and register vertex value
322
323                if L is None: continue #In case there are unused points
324
325                contributions = []
326                for volume_id, vertex_id in L:
327                    v = self.vertex_values[volume_id, vertex_id]
328                    contributions.append(v)
329
330                A[k] = reduction(contributions)
331
332
333            #if xy is True:
334            if x is True:
335                 #X = self.domain.coordinates[:,0].astype(precision)
336                 X = self.domain.coordinates[:].astype(precision)
337                 #Y = self.domain.coordinates[:,1].astype(precision)
338
339                 #return X, Y, A, V
340                 return X, A, V
341           
342            #else:
343            return A, V
344        else:
345            #Don't smooth
346            #obj machinery moved to general_mesh
347
348            # Create a V like [[0 1 2], [3 4 5]....[3*m-2 3*m-1 3*m]]
349            # These vert_id's will relate to the verts created below
350            #m = len(self.domain)  #Number of volumes
351            #M = 3*m        #Total number of unique vertices
352            #V = reshape(array(range(M)).astype(Int), (m,3))
353
354            #V = self.domain.get_triangles(obj=True)
355            V = self.domain.get_vertices
356            #FIXME use get_vertices, when ready
357
358            A = self.vertex_values.flat
359
360            #Do vertex coordinates
361            #if xy is True:
362            if x is True:
363                C = self.domain.get_vertex_coordinates()
364
365                X = C[:,0:6:2].copy()
366                Y = C[:,1:6:2].copy()
367
368                return X.flat, Y.flat, A, V
369            else:
370                return A, V
371
372
373class Conserved_quantity(Quantity):
374    """Class conserved quantity adds to Quantity:
375
376    storage and method for updating, and
377    methods for extrapolation from centropid to vertices inluding
378    gradients and limiters
379    """
380
381    def __init__(self, domain, vertex_values=None):
382        Quantity.__init__(self, domain, vertex_values)
383
384        from Numeric import zeros, Float
385       
386        #Allocate space for boundary values
387        #L = len(domain.boundary)
388        self.boundary_values = zeros(2, Float) #assumes no parrellism
389
390        #Allocate space for updates of conserved quantities by
391        #flux calculations and forcing functions
392       
393        N = domain.number_of_elements
394        self.explicit_update = zeros(N, Float )
395        self.semi_implicit_update = zeros(N, Float )
396
397        self.gradients = zeros(N, Float)
398        self.qmax = zeros(self.centroid_values.shape, Float)
399        self.qmin = zeros(self.centroid_values.shape, Float)
400
401        self.beta = domain.beta
402
403
404    def update(self, 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 = self.centroid_values.shape[0]
412
413        #Explicit updates
414        self.centroid_values += timestep*self.explicit_update
415
416        #Semi implicit updates
417        denominator = ones(N, Float)-timestep*self.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            self.centroid_values /= denominator
425
426
427    def compute_gradients(self):
428        """Compute gradients of piecewise linear function defined by centroids of
429        neighbouring volumes.
430        """
431
432
433        from Numeric import array, zeros, Float
434
435        N = self.centroid_values.shape[0]
436
437
438        G = self.gradients
439        Q = self.centroid_values
440        X = self.domain.centroids
441
442        for k in range(N):
443
444            # first and last elements have boundaries
445
446            if k == 0:
447
448                #Get data
449                k0 = k
450                k1 = k+1
451
452                q0 = Q[k0]
453                q1 = Q[k1]
454
455                x0 = X[k0] #V0 centroid
456                x1 = X[k1] #V1 centroid
457
458                #Gradient
459                G[k] = (q1 - q0)/(x1 - x0)
460
461            elif k == N-1:
462
463                #Get data
464                k0 = k
465                k1 = k-1
466
467                q0 = Q[k0]
468                q1 = Q[k1]
469
470                x0 = X[k0] #V0 centroid
471                x1 = X[k1] #V1 centroid
472
473                #Gradient
474                G[k] = (q1 - q0)/(x1 - x0)
475
476            else:
477                #Interior Volume (2 neighbours)
478
479                #Get data
480                k0 = k-1
481                k2 = k+1
482
483                q0 = Q[k0]
484                q1 = Q[k]
485                q2 = Q[k2]
486
487                x0 = X[k0] #V0 centroid
488                x1 = X[k] #V1 centroid (Self)
489                x2 = X[k2] #V2 centroid
490
491                #Gradient
492                G[k] = ((q0-q1)/(x0-x1)*(x2-x1) - (q2-q1)/(x2-x1)*(x0-x1))/(x2-x0)
493
494        return G
495
496    def extrapolate_first_order(self):
497        """Extrapolate conserved quantities from centroid to
498        vertices for each volume using
499        first order scheme.
500        """
501
502        qc = self.centroid_values
503        qv = self.vertex_values
504
505        for i in range(2):
506            qv[:,i] = qc
507
508
509    def extrapolate_second_order(self):
510        """Extrapolate conserved quantities from centroid to
511        vertices for each volume using
512        second order scheme.
513        """
514
515        Z = self.gradients
516        #print "gradients 1",Z
517       
518        self.compute_gradients()
519        #print "gradients 2", Z
520
521        G = self.gradients
522        V = self.domain.vertices
523        Qc = self.centroid_values
524        Qv = self.vertex_values
525       
526        #Check each triangle
527        for k in range(self.domain.number_of_elements):
528            #Centroid coordinates
529            x = self.domain.centroids[k]
530
531            #vertex coordinates
532            x0, x1 = V[k,:]
533
534            #Extrapolate
535            Qv[k,0] = Qc[k] + G[k]*(x0-x)
536            Qv[k,1] = Qc[k] + G[k]*(x1-x)
537
538    """    def limit(self):
539        Limit slopes for each volume to eliminate artificial variance
540        introduced by e.g. second order extrapolator
541
542        This is an unsophisticated limiter as it does not take into
543        account dependencies among quantities.
544
545        precondition:
546        vertex values are estimated from gradient
547        postcondition:
548        vertex values are updated
549       
550        print "in Q.limit"
551        from Numeric import zeros, Float
552
553        N = self.domain.number_of_elements
554        beta = self.beta
555
556        qc = self.centroid_values
557        qv = self.vertex_values
558
559        #Find min and max of this and neighbour's centroid values
560        qmax = self.qmax
561        qmin = self.qmin
562
563        for k in range(N):
564            qmax[k] = qmin[k] = qc[k]
565
566            for i in [-1,1]:
567                n = k+i
568                if (n >= 0) & (n <= N-1):
569                    qn = qc[n] #Neighbour's centroid value
570
571                    qmin[k] = min(qmin[k], qn)
572                    qmax[k] = max(qmax[k], qn)
573
574        #Phi limiter
575        for k in range(N):
576
577            #Diffences between centroids and maxima/minima
578            dqmax = qmax[k] - qc[k]
579            dqmin = qmin[k] - qc[k]
580
581            #Deltas between vertex and centroid values
582            dq = [0.0, 0.0]
583            for i in range(2):
584                dq[i] = qv[k,i] - qc[k]
585
586            #Find the gradient limiter (phi) across vertices
587            phi = 1.0
588            for i in range(2):
589                r = 1.0
590                if (dq[i] > 0): r = dqmax/dq[i]
591                if (dq[i] < 0): r = dqmin/dq[i]
592
593                phi = min( min(r*beta, 1), phi )
594
595            #Then update using phi limiter
596            for i in range(2):
597                qv[k,i] = qc[k] + phi*dq[i]
598    """
599
600    def limit(self):
601        """Limit slopes for each volume to eliminate artificial variance
602        introduced by e.g. second order extrapolator
603
604        This is an unsophisticated limiter as it does not take into
605        account dependencies among quantities.
606
607        precondition:
608        vertex values are estimated from gradient
609        postcondition:
610        vertex values are updated
611        """
612
613        from Numeric import zeros, Float
614
615        N = self.domain.number_of_elements
616        beta = self.beta
617
618        qc = self.centroid_values
619        qv = self.vertex_values
620
621        #Find min and max of this and neighbour's centroid values
622        qmax = self.qmax
623        qmin = self.qmin
624
625        for k in range(N):
626            qmax[k] = qmin[k] = qc[k]
627            for i in range(2):
628                n = self.domain.neighbours[k,i]
629                if n >= 0:
630                    qn = qc[n] #Neighbour's centroid value
631
632                    qmin[k] = min(qmin[k], qn)
633                    qmax[k] = max(qmax[k], qn)
634
635
636        #Diffences between centroids and maxima/minima
637        dqmax = qmax - qc
638        dqmin = qmin - qc
639
640        #Deltas between vertex and centroid values
641        dq = zeros(qv.shape, Float)
642        for i in range(2):
643            dq[:,i] = qv[:,i] - qc
644
645        #Phi limiter
646        for k in range(N):
647
648            #Find the gradient limiter (phi) across vertices
649            phi = 1.0
650            for i in range(2):
651                r = 1.0
652                if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
653                if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
654
655                phi = min( min(r*beta, 1), phi )
656
657            #Then update using phi limiter
658            for i in range(2):
659                qv[k,i] = qc[k] + phi*dq[k,i]
660
661       
662
663def newLinePlot(title='Simple Plot'):
664    import Gnuplot
665    g = Gnuplot.Gnuplot()
666    g.title(title)
667    g('set data style linespoints') 
668    g.xlabel('x')
669    g.ylabel('y')
670    return g
671
672def linePlot(g,x,y):
673    import Gnuplot
674    g.plot(Gnuplot.PlotItems.Data(x.flat,y.flat))
675
676
677
678
679
680if __name__ == "__main__":
681    from domain import Domain
682    from Numeric import arange
683   
684    points1 = [0.0, 1.0, 2.0, 3.0]
685    vertex_values = [[1.0,2.0],[4.0,5.0],[-1.0,2.0]]
686
687    D1 = Domain(points1)
688
689    Q1 = Conserved_quantity(D1, vertex_values)
690
691    print Q1.vertex_values
692    print Q1.centroid_values
693
694    new_vertex_values = [[2.0,1.0],[3.0,4.0],[-2.0,4.0]]
695
696    Q1.set_values(new_vertex_values)
697
698    print Q1.vertex_values
699    print Q1.centroid_values
700
701    new_centroid_values = [20,30,40]
702    Q1.set_values(new_centroid_values,'centroids')
703
704    print Q1.vertex_values
705    print Q1.centroid_values
706
707    class FunClass:
708        def __init__(self,value):
709            self.value = value
710
711        def __call__(self,x):
712            return self.value*(x**2)
713
714
715    fun = FunClass(1.0)
716    Q1.set_values(fun,'vertices')
717
718    print Q1.vertex_values
719    print Q1.centroid_values
720
721    Xc = Q1.domain.vertices
722    Qc = Q1.vertex_values
723    print Xc
724    print Qc
725
726    Qc[1,0] = 3
727
728    Q1.beta = 1.0
729    Q1.extrapolate_second_order()
730    Q1.limit()
731
732    g1 = newLinePlot('plot 1')
733    linePlot(g1,Xc,Qc)
734
735    points2 = arange(10)
736    D2 = Domain(points2)
737
738    Q2 = Conserved_quantity(D2)
739    Q2.set_values(fun,'vertices')
740    Xc = Q2.domain.vertices
741    Qc = Q2.vertex_values
742
743    g2 = newLinePlot('plot 2')
744    linePlot(g2,Xc,Qc)
745
746
747   
748    Q2.extrapolate_second_order()
749    Q2.limit()
750    Xc = Q2.domain.vertices
751    Qc = Q2.vertex_values
752
753    print Q2.centroid_values
754    print Qc
755    raw_input('press_return')
756   
757    g3 = newLinePlot('plot 3')
758    linePlot(g3,Xc,Qc)
759    raw_input('press return')
760
761
762    for i in range(10):
763        fun = FunClass(i/10.0)
764        Q2.set_values(fun,'vertices')
765        Qc = Q2.vertex_values
766        linePlot(g3,Xc,Qc)
767
768    raw_input('press return')
769
Note: See TracBrowser for help on using the repository browser.