source: development/pyvolution-1d/domain.py @ 3510

Last change on this file since 3510 was 3510, checked in by jakeman, 18 years ago

Files now contain inmplementation of method in which advection terms and
pressure terms are split. problems are present.

File size: 33.4 KB
RevLine 
[3293]1"""Class Domain - 1D domains for finite-volume computations of
2   the shallow water wave equation
3
4
5   Copyright 2004
6   Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
7   Geoscience Australia
8"""
9from generic_boundary_conditions import *
10from coordinate_transforms.geo_reference import Geo_reference
11
12class Domain:
13
14    def __init__(self, coordinates, boundary = None,
15                 conserved_quantities = None, other_quantities = None,
16                 tagged_elements = None, geo_reference = None):
17        """
18        Build 1D elements from x coordinates
19        """
20
21        from Numeric import array, zeros, Float, Int
22
23        self.beta = 1.0
[3510]24        #self.limiter = "minmod_kurganov"
25        #self.limiter_type = "steven"
26        self.fluxfunc = "unsplit"
27       
[3293]28        #Store Points
29        self.coordinates = array(coordinates)
30
31        if geo_reference is None:
32            self.geo_reference = Geo_reference() #Use defaults
33        else:
34            self.geo_reference = geo_reference
35
36        #Register number of Elements
37        self.number_of_elements = N = len(self.coordinates)-1
38
39        #Allocate space for neighbour and boundary structures
40        self.neighbours = zeros((N, 2), Int)
41        #self.neighbour_edges = zeros((N, 2), Int)
42        self.neighbour_vertices = zeros((N, 2), Int)
43        self.number_of_boundaries = zeros(N, Int)
44        self.surrogate_neighbours = zeros((N, 2), Int)
45       
46        #Allocate space for geometric quantities
47        self.vertices  = zeros((N, 2), Float)
48        self.centroids = zeros(N, Float)
49        self.areas     = zeros(N, Float)
50
51        self.normals = zeros((N, 2), Float)
52       
53        for i in range(N):
54            xl = self.coordinates[i]
55            xr = self.coordinates[i+1]
56            self.vertices[i,0] = xl
57            self.vertices[i,1] = xr
58
59            centroid = (xl+xr)/2
60            self.centroids[i] = centroid
61
62            msg = 'Coordinates should be ordered, smallest to largest'
63            assert xr>xl, msg
64           
65            #The normal vectors
66            # - point outward from each edge
67            # - are orthogonal to the edge
68            # - have unit length
69            # - Are enumerated by left vertex then right vertex normals
70
71            nl = -1.0
72            nr =  1.0
73            self.normals[i,:] = [nl, nr]
74
75            self.areas[i] = (xr-xl)
76           
77##         print 'N', N
78##         print 'Centroid', self.centroids
79##         print 'Areas', self.areas
80##         print 'Vertex_Coordinates', self.vertices
81           
82            #Initialise Neighbours (-1 means that it is a boundary neighbour)
83            self.neighbours[i, :] = [-1, -1]
84            #Initialise edge ids of neighbours
85            #Initialise vertex ids of neighbours
86            #In case of boundaries this slot is not used
87            #self.neighbour_edges[i, :] = [-1, -1]
88            self.neighbour_vertices[i, :] = [-1, -1]
89
90        self.build_vertexlist()
91
92        #Build neighbour structure
93        self.build_neighbour_structure()
94
95        #Build surrogate neighbour structure
96        self.build_surrogate_neighbour_structure()         
97
98        #Build boundary dictionary mapping (id, edge) to symbolic tags
99        #Build boundary dictionary mapping (id, vertex) to symbolic tags
100        self.build_boundary_dictionary(boundary)
101
102        #Build tagged element  dictionary mapping (tag) to array of elements
103        self.build_tagged_elements_dictionary(tagged_elements)
104       
105        from quantity import Quantity, Conserved_quantity
106       
107        #List of quantity names entering
108        #the conservation equations
109        #(Must be a subset of quantities)
110        if conserved_quantities is None:
111            self.conserved_quantities = []
112        else:
113            self.conserved_quantities = conserved_quantities
114
115        if other_quantities is None:
116            self.other_quantities = []
117        else:
118            self.other_quantities = other_quantities
119
120
121        #Build dictionary of Quantity instances keyed by quantity names
122        self.quantities = {}
123
124        #FIXME: remove later - maybe OK, though....
125        for name in self.conserved_quantities:
126            self.quantities[name] = Conserved_quantity(self)
127        for name in self.other_quantities:
128            self.quantities[name] = Quantity(self)
129
130        #Create an empty list for explicit forcing terms
131        self.forcing_terms = []
132
133        #Defaults
134        from config import max_smallsteps, beta_w, beta_h, epsilon, CFL
135        self.beta_w = beta_w
136        self.beta_h = beta_h
137        self.epsilon = epsilon
138
139        #FIXME: Maybe have separate orders for h-limiter and w-limiter?
140        #Or maybe get rid of order altogether and use beta_w and beta_h
141        self.default_order = 1
142        self.order = self.default_order
143        self.smallsteps = 0
144        self.max_smallsteps = max_smallsteps
145        self.number_of_steps = 0
146        self.number_of_first_order_steps = 0
147        self.CFL = CFL
148
149        #Model time
150        self.time = 0.0
151        self.finaltime = None
152        self.min_timestep = self.max_timestep = 0.0
153        self.starttime = 0 #Physical starttime if any (0 is 1 Jan 1970 00:00:00)
154        #Checkpointing and storage
155        from config import default_datadir
156        self.datadir = default_datadir
157        self.filename = 'domain'
158        self.checkpoint = False
159
160    def __len__(self):
161        return self.number_of_elements
162
163    def build_vertexlist(self):
164        """Build vertexlist index by vertex ids and for each entry (point id)
165        build a list of (triangles, vertex_id) pairs that use the point
166        as vertex.
167
168        Preconditions:
169          self.coordinates and self.triangles are defined
170
171        Postcondition:
172          self.vertexlist is built
173        """
174        from Numeric import array
175
176        vertexlist = [None]*len(self.coordinates)
177        for i in range(self.number_of_elements):
178
179            #a = self.triangles[i, 0]
180            #b = self.triangles[i, 1]
181            #c = self.triangles[i, 2]
182            a = i
183            b = i + 1
184
185            #Register the vertices v as lists of
186            #(triangle_id, vertex_id) tuples associated with them
187            #This is used for smoothing
188            #for vertex_id, v in enumerate([a,b,c]):
189            for vertex_id, v in enumerate([a,b]):
190                if vertexlist[v] is None:
191                    vertexlist[v] = []
192
193                vertexlist[v].append( (i, vertex_id) )
194
195        self.vertexlist = vertexlist
196
197       
198    def build_neighbour_structure(self):
199        """Update all registered triangles to point to their neighbours.
200
201        Also, keep a tally of the number of boundaries for each triangle
202
203        Postconditions:
204          neighbours and neighbour_edges is populated
205          neighbours and neighbour_vertices is populated
206          number_of_boundaries integer array is defined.
207        """
208
209        #Step 1:
210        #Build dictionary mapping from segments (2-tuple of points)
211        #to left hand side edge (facing neighbouring triangle)
212
213        N = self.number_of_elements
214        neighbourdict = {}
215        #l_edge = 0
216        #r_edge = 1
217        l_vertex = 0
218        r_vertex = 1
219        for i in range(N):
220
221            #Register all segments as keys mapping to current triangle
222            #and segment id
223            #a = self.triangles[i, 0]
224            #b = self.triangles[i, 1]
225            #c = self.triangles[i, 2]
226            a = self.vertices[i,0]
227            b = self.vertices[i,1]
228           
229            """
230            if neighbourdict.has_key((a,b)):
231                    msg = "Edge 2 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[a,b][1],neighbourdict[a,b][0])
232                    raise msg
233            if neighbourdict.has_key((b,c)):
234                    msg = "Edge 0 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[b,c][1],neighbourdict[b,c][0])
235                    raise msg
236            if neighbourdict.has_key((c,a)):
237                    msg = "Edge 1 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[c,a][1],neighbourdict[c,a][0])
238                    raise msg
239                    """
240            #neighbourdict[a,b] = (i, 2) #(id, edge)
241            #neighbourdict[b,c] = (i, 0) #(id, edge)
242            #neighbourdict[c,a] = (i, 1) #(id, edge)
243            #neighbourdict[a,b] = (i, 1) #(id, edge)
244            #neighbourdict[b,a] = (i, 0) #(id, edge)
245            #neighbourdict[a,l_edge] = (i, 0) #(id, edge)
246            #neighbourdict[b,r_edge] = (i, 1) #(id, edge)
247            neighbourdict[a,l_vertex] = (i, 0) #(id, vertex)
248            neighbourdict[b,r_vertex] = (i, 1) #(id, vertex)
249
250
251        #Step 2:
252        #Go through triangles again, but this time
253        #reverse direction of segments and lookup neighbours.
254        for i in range(N):
255            #a = self.triangles[i, 0]
256            #b = self.triangles[i, 1]
257            #c = self.triangles[i, 2]
258           
259            a = self.vertices[i,0]
260            b = self.vertices[i,1]
261           
262            #self.number_of_boundaries[i] = 3
263            self.number_of_boundaries[i] = 2
264            #if neighbourdict.has_key((b,l_edge)):
265            if neighbourdict.has_key((b,l_vertex)):
266                #self.neighbours[i, 1] = neighbourdict[b,l_edge][0]
267                #self.neighbour_edges[i, 1] = neighbourdict[b,l_edge][1]
268                self.neighbours[i, 1] = neighbourdict[b,l_vertex][0]
269                self.neighbour_vertices[i, 1] = neighbourdict[b,l_vertex][1]
270                self.number_of_boundaries[i] -= 1
271
272            #if neighbourdict.has_key((a,r_edge)):
273            if neighbourdict.has_key((a,r_vertex)):
274                #self.neighbours[i, 0] = neighbourdict[a,r_edge][0]
275                #self.neighbour_edges[i, 0] = neighbourdict[a,r_edge][1]
276                self.neighbours[i, 0] = neighbourdict[a,r_vertex][0]
277                self.neighbour_vertices[i, 0] = neighbourdict[a,r_vertex][1]
278                self.number_of_boundaries[i] -= 1
279               
280            #if neighbourdict.has_key((b,a)):
281            #    self.neighbours[i, 1] = neighbourdict[b,a][0]
282            #    self.neighbour_edges[i, 1] = neighbourdict[b,a][1]
283            #    self.number_of_boundaries[i] -= 1
284
285            #if neighbourdict.has_key((c,b)):
286            #    self.neighbours[i, 0] = neighbourdict[c,b][0]
287            #    self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
288            #    self.number_of_boundaries[i] -= 1
289
290            #if neighbourdict.has_key((a,b)):
291            #    self.neighbours[i, 0] = neighbourdict[a,b][0]
292            #    self.neighbour_edges[i, 0] = neighbourdict[a,b][1]
293            #    self.number_of_boundaries[i] -= 1
294
295    def build_surrogate_neighbour_structure(self):
296        """Build structure where each triangle edge points to its neighbours
297        if they exist. Otherwise point to the triangle itself.
298
299        The surrogate neighbour structure is useful for computing gradients
300        based on centroid values of neighbours.
301
302        Precondition: Neighbour structure is defined
303        Postcondition:
304          Surrogate neighbour structure is defined:
305          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
306          triangles.
307
308        """
309
310        N = self.number_of_elements
311        for i in range(N):
312            #Find all neighbouring volumes that are not boundaries
313            #for k in range(3):
314            for k in range(2):   
315                if self.neighbours[i, k] < 0:
316                    self.surrogate_neighbours[i, k] = i #Point this triangle
317                else:
318                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
319
320    def build_boundary_dictionary(self, boundary = None):
321        """Build or check the dictionary of boundary tags.
322         self.boundary is a dictionary of tags,
323         keyed by volume id and edge:
324         { (id, edge): tag, ... }
325
326         Postconditions:
327            self.boundary is defined.
328        """
329
330        from config import default_boundary_tag
331
332        if boundary is None:
333            boundary = {}
334            for vol_id in range(self.number_of_elements):
335                #for edge_id in range(0, 3):
336                #for edge_id in range(0, 2):
337                for vertex_id in range(0, 2):   
338                    #if self.neighbours[vol_id, edge_id] < 0:
339                    if self.neighbours[vol_id, vertex_id] < 0:   
340                        #boundary[(vol_id, edge_id)] = default_boundary_tag
341                        boundary[(vol_id, vertex_id)] = default_boundary_tag
342        else:
343            #Check that all keys in given boundary exist
344            #for vol_id, edge_id in boundary.keys():
345            for vol_id, vertex_id in boundary.keys():   
346                #msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
347                msg = 'Segment (%d, %d) does not exist' %(vol_id, vertex_id)
348                a, b = self.neighbours.shape
349                #assert vol_id < a and edge_id < b, msg
350                assert vol_id < a and vertex_id < b, msg
351
352                #FIXME: This assert violates internal boundaries (delete it)
353                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
354                #assert self.neighbours[vol_id, edge_id] < 0, msg
355
356            #Check that all boundary segments are assigned a tag
357            for vol_id in range(self.number_of_elements):
358                #for edge_id in range(0, 3):
359                #for edge_id in range(0, 2):
360                for vertex_id in range(0, 2):   
361                    #if self.neighbours[vol_id, edge_id] < 0:
362                    if self.neighbours[vol_id, vertex_id] < 0:   
363                        #if not boundary.has_key( (vol_id, edge_id) ):
364                        if not boundary.has_key( (vol_id, vertex_id) ):   
365                            msg = 'WARNING: Given boundary does not contain '
366                            #msg += 'tags for edge (%d, %d). '\
367                            #       %(vol_id, edge_id)
368                            msg += 'tags for vertex (%d, %d). '\
369                                   %(vol_id, vertex_id)
370                            msg += 'Assigning default tag (%s).'\
371                                   %default_boundary_tag
372
373                            #FIXME: Print only as per verbosity
374                            #print msg
375
376                            #FIXME: Make this situation an error in the future
377                            #and make another function which will
378                            #enable default boundary-tags where
379                            #tags a not specified
380                            #boundary[ (vol_id, edge_id) ] =\
381                            boundary[ (vol_id, vertex_id) ] =\
382                                      default_boundary_tag
383
384
385
386        self.boundary = boundary
387
388    def build_tagged_elements_dictionary(self, tagged_elements = None):
389        """Build the dictionary of element tags.
390         self.tagged_elements is a dictionary of element arrays,
391         keyed by tag:
392         { (tag): [e1, e2, e3..] }
393
394         Postconditions:
395            self.element_tag is defined
396        """
397        from Numeric import array, Int
398
399        if tagged_elements is None:
400            tagged_elements = {}
401        else:
402            #Check that all keys in given boundary exist
403            for tag in tagged_elements.keys():
404                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
405
406                msg = 'Not all elements exist. '
407                assert max(tagged_elements[tag]) < self.number_of_elements, msg
408        #print "tagged_elements", tagged_elements
409        self.tagged_elements = tagged_elements
410
411    def get_boundary_tags(self):
412        """Return list of available boundary tags
413        """
414
415        tags = {}
416        for v in self.boundary.values():
417            tags[v] = 1
418
419        return tags.keys()
420       
421    def get_vertex_coordinates(self, obj = False):
422        """Return all vertex coordinates.
423        Return all vertex coordinates for all triangles as an Nx6 array
424        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
425
426        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
427        FIXME, we might make that the default.
428        FIXME Maybe use keyword: continuous = False for this condition?
429
430       
431        """
432
433        if obj is True:
434            from Numeric import concatenate, reshape
435            #V = self.vertex_coordinates
436            V = self.vertices
437            #return concatenate( (V[:,0:2], V[:,2:4], V[:,4:6]), axis=0)
438
439            N = V.shape[0]
440            #return reshape(V, (3*N, 2))
441            return reshape(V, (N, 2))
442        else:   
443            #return self.vertex_coordinates
444            return self.vertices
445   
446    def get_conserved_quantities(self, vol_id, vertex=None):#, edge=None):
447        """Get conserved quantities at volume vol_id
448
449        If vertex is specified use it as index for vertex values
450        If edge is specified use it as index for edge values
451        If neither are specified use centroid values
452        If both are specified an exeception is raised
453
454        Return value: Vector of length == number_of_conserved quantities
455
456        """
457
458        from Numeric import zeros, Float
459
460        #if not (vertex is None):# or edge is None):
461        #    msg = 'Values for both vertex and edge was specified.'
462        #    msg += 'Only one (or none) is allowed.'
463       #     raise msg
464
465        q = zeros( len(self.conserved_quantities), Float)
466
467        for i, name in enumerate(self.conserved_quantities):
468            Q = self.quantities[name]
469            if vertex is not None:
470                q[i] = Q.vertex_values[vol_id, vertex]
471            #elif edge is not None:
472            #    q[i] = Q.edge_values[vol_id, edge]
473            else:
474                q[i] = Q.centroid_values[vol_id]
475
476        return q
477       
478
479    def get_centroids(self):
480        """Return all coordinates of centroids
481        Return x coordinate of centroid for each element as a N array
482        """
483
484        return self.centroids
485
486    def get_vertices(self):
487        """Return all coordinates of centroids
488        Return x coordinate of centroid for each element as a N array
489        """
490
491        return self.vertices
492
493    def get_coordinate(self, elem_id, vertex=None):
494        """Return coordinate of centroid,
495        or left or right vertex.
496        Left vertex (vertex=0). Right vertex (vertex=1)
497        """
498
499        if vertex is None:
500            return self.centroids[elem_id]
501        else:
502            return self.vertices[elem_id,vertex]
503
504    def get_area(self, elem_id):
505        """Return area of element id
506        """
507
508        return self.areas[elem_id]
509
510    def get_quantity(self, name, location='vertices', indices = None):
511        """Get values for named quantity
512
513        name: Name of quantity
514
515        In case of location == 'centroids' the dimension values must
516        be a list of a Numerical array of length N, N being the number
517        of elements. Otherwise it must be of dimension Nx3.
518
519        Indices is the set of element ids that the operation applies to.
520
521        The values will be stored in elements following their
522        internal ordering.
523        """
524
525        return self.quantities[name].get_values( location, indices = indices)
526
527    def get_centroid_coordinates(self):
528        """Return all centroid coordinates.
529        Return all centroid coordinates for all triangles as an Nx2 array
530        (ordered as x0, y0 for each triangle)
531        """
532        return self.centroids
533
534    def set_quantity(self, name, *args, **kwargs):
535        """Set values for named quantity
536
537
538        One keyword argument is documented here:
539        expression = None, # Arbitrary expression
540
541        expression:
542          Arbitrary expression involving quantity names
543
544        See Quantity.set_values for further documentation.
545        """
546
547        #FIXME (Ole): Allow new quantities here
548        #from quantity import Quantity, Conserved_quantity
549        #Create appropriate quantity object
550        ##if name in self.conserved_quantities:
551        ##    self.quantities[name] = Conserved_quantity(self)
552        ##else:
553        ##    self.quantities[name] = Quantity(self)
554
555
556        #Do the expression stuff
557        if kwargs.has_key('expression'):
558            expression = kwargs['expression']
559            del kwargs['expression']
560
561            Q = self.create_quantity_from_expression(expression)
562            kwargs['quantity'] = Q
[3295]563       
[3293]564        #Assign values
565        self.quantities[name].set_values(*args, **kwargs)
566
567    def set_boundary(self, boundary_map):
568        """Associate boundary objects with tagged boundary segments.
569
570        Input boundary_map is a dictionary of boundary objects keyed
571        by symbolic tags to matched against tags in the internal dictionary
572        self.boundary.
573
574        As result one pointer to a boundary object is stored for each vertex
575        in the list self.boundary_objects.
576        More entries may point to the same boundary object
577
578        Schematically the mapping is from two dictionaries to one list
579        where the index is used as pointer to the boundary_values arrays
580        within each quantity.
581
582        self.boundary:          (vol_id, edge_id): tag
583        boundary_map (input):   tag: boundary_object
584        ----------------------------------------------
585        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
586
587
588        Pre-condition:
589          self.boundary has been built.
590
591        Post-condition:
592          self.boundary_objects is built
593
594        If a tag from the domain doesn't appear in the input dictionary an
595        exception is raised.
596        However, if a tag is not used to the domain, no error is thrown.
597        FIXME: This would lead to implementation of a
598        default boundary condition
599
600        Note: If a segment is listed in the boundary dictionary and if it is
601        not None, it *will* become a boundary -
602        even if there is a neighbouring triangle.
603        This would be the case for internal boundaries
604
605        Boundary objects that are None will be skipped.
606
607        FIXME: If set_boundary is called multiple times and if Boundary
608        object is changed into None, the neighbour structure will not be
609        restored!!!
610        """
611
612        self.boundary_objects = []
613       
614
615
616
617       
618        self.boundary_map = boundary_map  #Store for use with eg. boundary_stats.
619
620        #FIXME: Try to remove the sorting and fix test_mesh.py
621        x = self.boundary.keys()
622        x.sort()
623
624        #Loop through edges that lie on the boundary and associate them with
625        #callable boundary objects depending on their tags
626        #for k, (vol_id, edge_id) in enumerate(x):
627        for k, (vol_id, vertex_id) in enumerate(x):
628            #tag = self.boundary[ (vol_id, edge_id) ]
629            tag = self.boundary[ (vol_id, vertex_id) ]
630
631            if boundary_map.has_key(tag):
632                B = boundary_map[tag]  #Get callable boundary object
633
634                if B is not None:
635                    #self.boundary_objects.append( ((vol_id, edge_id), B) )
636                    #self.neighbours[vol_id, edge_id] = -len(self.boundary_objects)
637                    self.boundary_objects.append( ((vol_id, vertex_id), B) )
638                    self.neighbours[vol_id, vertex_id] = -len(self.boundary_objects)
639                else:
640                    pass
641                    #FIXME: Check and perhaps fix neighbour structure
642
643            else:
644                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
645                msg += 'bound to a boundary object.\n'
646                msg += 'All boundary tags defined in domain must appear '
647                msg += 'in the supplied dictionary.\n'
648                msg += 'The tags are: %s' %self.get_boundary_tags()
649                raise msg
650
651
652
653    def check_integrity(self):
654        #Mesh.check_integrity(self)
655       
656        for quantity in self.conserved_quantities:
657            msg = 'Conserved quantities must be a subset of all quantities'
658            assert quantity in self.quantities, msg
659
660        ##assert hasattr(self, 'boundary_objects')
661
[3335]662    def write_time(self):
663        print self.timestepping_statistics()
664
665    def timestepping_statistics(self):
666        """Return string with time stepping statistics for printing or logging
667        """
668
669        msg = ''
670        if self.min_timestep == self.max_timestep:
671            msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
672                   %(self.time, self.min_timestep, self.number_of_steps,
673                     self.number_of_first_order_steps)
674        elif self.min_timestep > self.max_timestep:
675            msg += 'Time = %.4f, steps=%d (%d)'\
676                   %(self.time, self.number_of_steps,
677                     self.number_of_first_order_steps)
678        else:
679            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
680                   %(self.time, self.min_timestep,
681                     self.max_timestep, self.number_of_steps,
682                     self.number_of_first_order_steps)
683
684        return msg
685
[3293]686    def get_name(self):
687        return self.filename
688
689    def set_name(self, name):
690        self.filename = name
691
692    def get_datadir(self):
693        return self.datadir
694
695    def set_datadir(self, name):
696        self.datadir = name
697
698#Main components of evolve
699    def evolve(self, yieldstep = None, finaltime = None,
700               skip_initial_step = False):
701        """Evolve model from time=0.0 to finaltime yielding results
702        every yieldstep.
703
704        Internally, smaller timesteps may be taken.
705
706        Evolve is implemented as a generator and is to be called as such, e.g.
707
708        for t in domain.evolve(timestep, yieldstep, finaltime):
709            <Do something with domain and t>
710
711        """
712
713        from config import min_timestep, max_timestep, epsilon
714
715        #FIXME: Maybe lump into a larger check prior to evolving
716        msg = 'Boundary tags must be bound to boundary objects before evolving system, '
717        msg += 'e.g. using the method set_boundary.\n'
718        msg += 'This system has the boundary tags %s ' %self.get_boundary_tags()
719        assert hasattr(self, 'boundary_objects'), msg
720
721        ##self.set_defaults()
722
723        if yieldstep is None:
724            yieldstep = max_timestep
725        else:
726            yieldstep = float(yieldstep)
727
728        self.order = self.default_order
729
730
731        self.yieldtime = 0.0 #Time between 'yields'
732
733        #Initialise interval of timestep sizes (for reporting only)
734        # SEEMS WIERD
735        self.min_timestep = max_timestep
736        self.max_timestep = min_timestep
737        self.finaltime = finaltime
738        self.number_of_steps = 0
739        self.number_of_first_order_steps = 0
740
741        #update ghosts
742        #self.update_ghosts()
[3335]743   
[3293]744        #Initial update of vertex and edge values
745        self.distribute_to_vertices_and_edges()
[3335]746       
[3293]747        #Initial update boundary values
748        self.update_boundary()
749
750        #Or maybe restore from latest checkpoint
751        if self.checkpoint is True:
752            self.goto_latest_checkpoint()
753
754        if skip_initial_step is False:
755            yield(self.time)  #Yield initial values
756
757        while True:
758
759            #Compute fluxes across each element edge
760            self.compute_fluxes()
761
762            #Update timestep to fit yieldstep and finaltime
763            self.update_timestep(yieldstep, finaltime)
764
765            #Update conserved quantities
766            self.update_conserved_quantities()
767
768            #update ghosts
769            #self.update_ghosts()
770
771            #Update vertex and edge values
772            self.distribute_to_vertices_and_edges()
773
774            #Update boundary values
775            self.update_boundary()
776
777            #print 'timestep', self.timestep
778
779            #Update time
780            self.time += self.timestep
781            self.yieldtime += self.timestep
782            self.number_of_steps += 1
783            if self.order == 1:
784                self.number_of_first_order_steps += 1
785
786            #Yield results
787            if finaltime is not None and abs(self.time - finaltime) < epsilon:
788
789                #FIXME: There is a rare situation where the
790                #final time step is stored twice. Can we make a test?
791
792                # Yield final time and stop
793                yield(self.time)
794                break
795
796
797            if abs(self.yieldtime - yieldstep) < epsilon:
798                # Yield (intermediate) time and allow inspection of domain
799
800                if self.checkpoint is True:
801                    self.store_checkpoint()
802                    self.delete_old_checkpoints()
803
804                #Pass control on to outer loop for more specific actions
805                yield(self.time)
806
807                # Reinitialise
808                self.yieldtime = 0.0
809                self.min_timestep = max_timestep
810                self.max_timestep = min_timestep
811                self.number_of_steps = 0
812                self.number_of_first_order_steps = 0
813
814    def distribute_to_vertices_and_edges(self):
815        """Extrapolate conserved quantities from centroid to
816        vertices and edge-midpoints for each volume
817
818        Default implementation is straight first order,
819        i.e. constant values throughout each element and
820        no reference to non-conserved quantities.
821        """
822
823        for name in self.conserved_quantities:
824            Q = self.quantities[name]
825            if self.order == 1:
826                Q.extrapolate_first_order()
827            elif self.order == 2:
828                Q.extrapolate_second_order()
[3425]829                #Q.limit()
[3293]830            else:
831                raise 'Unknown order'
832            #Q.interpolate_from_vertices_to_edges()
833
834
835    def update_boundary(self):
836        """Go through list of boundary objects and update boundary values
837        for all conserved quantities on boundary.
838        """
839
840        #FIXME: Update only those that change (if that can be worked out)
841        #FIXME: Boundary objects should not include ghost nodes.
842        #for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
843        #    q = B.evaluate(vol_id, edge_id)
844        for i, ((vol_id, vertex_id), B) in enumerate(self.boundary_objects):
845            q = B.evaluate(vol_id, vertex_id)
846
847            for j, name in enumerate(self.conserved_quantities):
848                Q = self.quantities[name]
849                Q.boundary_values[i] = q[j]
850
851    def update_timestep(self, yieldstep, finaltime):
852
853        from config import min_timestep
854
855        # self.timestep is calculated from speed of characteristics
856        # Apply CFL condition here
857        timestep = self.CFL*self.timestep
858
859        #Record maximal and minimal values of timestep for reporting
860        self.max_timestep = max(timestep, self.max_timestep)
861        self.min_timestep = min(timestep, self.min_timestep)
862
863        #Protect against degenerate time steps
864        if timestep < min_timestep:
865
866            #Number of consecutive small steps taken b4 taking action
867            self.smallsteps += 1
868
869            if self.smallsteps > self.max_smallsteps:
870                self.smallsteps = 0 #Reset
871
872                if self.order == 1:
873                    msg = 'WARNING: Too small timestep %.16f reached '\
874                          %timestep
875                    msg += 'even after %d steps of 1 order scheme'\
876                           %self.max_smallsteps
877                    print msg
878                    timestep = min_timestep  #Try enforcing min_step
879
880                    #raise msg
881                else:
882                    #Try to overcome situation by switching to 1 order
[3335]883                    print "changing Order 1"
[3293]884                    self.order = 1
885
886        else:
887            self.smallsteps = 0
888            if self.order == 1 and self.default_order == 2:
889                self.order = 2
890
891
892        #Ensure that final time is not exceeded
893        if finaltime is not None and self.time + timestep > finaltime:
894            timestep = finaltime-self.time
895
896        #Ensure that model time is aligned with yieldsteps
897        if self.yieldtime + timestep > yieldstep:
898            timestep = yieldstep-self.yieldtime
899
900        self.timestep = timestep
901
902
903    def compute_forcing_terms(self):
904        """If there are any forcing functions driving the system
905        they should be defined in Domain subclass and appended to
906        the list self.forcing_terms
907        """
908
909        for f in self.forcing_terms:
910            f(self)
911           
912   
913    def update_conserved_quantities(self):
914        """Update vectors of conserved quantities using previously
915        computed fluxes specified forcing functions.
916        """
917
918        from Numeric import ones, sum, equal, Float
919
920        N = self.number_of_elements
921        d = len(self.conserved_quantities)
922
923        timestep = self.timestep
924
925        #Compute forcing terms
926        self.compute_forcing_terms()
927
928        #Update conserved_quantities
929        for name in self.conserved_quantities:
930            Q = self.quantities[name]
931            Q.update(timestep)
932
933            #Clean up
934            #Note that Q.explicit_update is reset by compute_fluxes
935
936            #MH090605 commented out the following since semi_implicit_update is now re-initialized
937            #at the end of the _update function in quantity_ext.c (This is called by the
938            #preceeding Q.update(timestep) statement above).
939            #For run_profile.py with N=128, the time of update_conserved_quantities is cut from 14.00 secs
940            #to 8.35 secs
941
942            #Q.semi_implicit_update[:] = 0.0
943
944if __name__ == "__main__":
945
946    points1 = [0.0, 1.0, 2.0, 3.0]
947    D1 = Domain(points1)
948
949    print D1.get_coordinate(0)
950    print D1.get_coordinate(0,1)
951    print 'Number of Elements = ',D1.number_of_elements
952
953    try:
954        print D1.get_coordinate(3)
955    except:
956        pass
957    else:
958        msg =  'Should have raised an out of bounds exception'
959        raise msg
960
961    #points2 = [0.0, 1.0, 2.0, 3.0, 2.5]
962    #D2 = Domain(points2)
963
Note: See TracBrowser for help on using the repository browser.