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

Last change on this file since 3322 was 3322, checked in by steve, 19 years ago

change to gnuplot

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