source: anuga_work/development/anuga_1d/domain.py @ 5738

Last change on this file since 5738 was 5738, checked in by steve, 16 years ago

Copied evolve code from anuga_2d

File size: 50.0 KB
Line 
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 *
10#from 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):
17        """
18        Build 1D elements from x coordinates
19        """
20
21        from Numeric import array, zeros, Float, Int
22       
23        #Store Points
24        self.coordinates = array(coordinates)
25
26# #        if geo_reference is None:
27# #            self. = Geo_reference() #Use defaults
28# #        else:
29# #            self.geo_reference = geo_reference
30
31        #Register number of Elements
32        self.number_of_elements = N = len(self.coordinates)-1
33
34        self.beta = 1.0
35        self.limiter = "minmod_kurganov"
36        self.wet_nodes = zeros((N,2), Int) # should this be here
37
38        #Allocate space for neighbour and boundary structures
39        self.neighbours = zeros((N, 2), Int)
40        #self.neighbour_edges = zeros((N, 2), Int)
41        self.neighbour_vertices = zeros((N, 2), Int)
42        self.number_of_boundaries = zeros(N, Int)
43        self.surrogate_neighbours = zeros((N, 2), Int)
44       
45        #Allocate space for geometric quantities
46        self.vertices  = zeros((N, 2), Float)
47        self.centroids = zeros(N, Float)
48        self.areas     = zeros(N, Float)
49
50        self.max_speed_array = zeros(N, Float)
51
52        self.normals = zeros((N, 2), Float)
53       
54        for i in range(N):
55            xl = self.coordinates[i]
56            xr = self.coordinates[i+1]
57            self.vertices[i,0] = xl
58            self.vertices[i,1] = xr
59
60            centroid = (xl+xr)/2.0
61            self.centroids[i] = centroid
62
63            msg = 'Coordinates should be ordered, smallest to largest'
64            assert xr>xl, msg
65           
66            #The normal vectors
67            # - point outward from each edge
68            # - are orthogonal to the edge
69            # - have unit length
70            # - Are enumerated by left vertex then right vertex normals
71
72            nl = -1.0
73            nr =  1.0
74            self.normals[i,:] = [nl, nr]
75
76            self.areas[i] = (xr-xl)
77           
78# #         print 'N', N
79# #         print 'Centroid', self.centroids
80# #         print 'Areas', self.areas
81# #         print 'Vertex_Coordinates', self.vertices
82           
83            #Initialise Neighbours (-1 means that it is a boundary neighbour)
84            self.neighbours[i, :] = [-1, -1]
85            #Initialise edge ids of neighbours
86            #Initialise vertex ids of neighbours
87            #In case of boundaries this slot is not used
88            #self.neighbour_edges[i, :] = [-1, -1]
89            self.neighbour_vertices[i, :] = [-1, -1]
90
91        self.build_vertexlist()
92
93        #Build neighbour structure
94        self.build_neighbour_structure()
95
96        #Build surrogate neighbour structure
97        self.build_surrogate_neighbour_structure()         
98
99        #Build boundary dictionary mapping (id, edge) to symbolic tags
100        #Build boundary dictionary mapping (id, vertex) to symbolic tags
101        self.build_boundary_dictionary(boundary)
102
103        #Build tagged element  dictionary mapping (tag) to array of elements
104        self.build_tagged_elements_dictionary(tagged_elements)
105       
106        from quantity import Quantity, Conserved_quantity
107        #from quantity_domain import Quantity, Conserved_quantity
108       
109        #List of quantity names entering
110        #the conservation equations
111        #(Must be a subset of quantities)
112        if conserved_quantities is None:
113            self.conserved_quantities = []
114        else:
115            self.conserved_quantities = conserved_quantities
116
117        if other_quantities is None:
118            self.other_quantities = []
119        else:
120            self.other_quantities = other_quantities
121
122
123        #Build dictionary of Quantity instances keyed by quantity names
124        self.quantities = {}
125
126        #FIXME: remove later - maybe OK, though....
127        for name in self.conserved_quantities:
128            self.quantities[name] = Quantity(self)
129        for name in self.other_quantities:
130            self.quantities[name] = Quantity(self)
131
132        #Create an empty list for explicit forcing terms
133        self.forcing_terms = []
134
135        #Defaults
136        from config import max_smallsteps, beta_w, beta_h, epsilon, CFL
137        self.beta_w = beta_w
138        self.beta_h = beta_h
139        self.epsilon = epsilon
140
141        #FIXME: Maybe have separate orders for h-limiter and w-limiter?
142        #Or maybe get rid of order altogether and use beta_w and beta_h
143        self.default_order = 1
144        self.order = self.default_order
145
146        self.default_time_order = 1
147        self.time_order = self.default_time_order
148       
149        self.smallsteps = 0
150        self.max_smallsteps = max_smallsteps
151        self.number_of_steps = 0
152        self.number_of_first_order_steps = 0
153        self.CFL = CFL
154
155        #Model time
156        self.time = 0.0
157        self.finaltime = None
158        self.min_timestep = self.max_timestep = 0.0
159        self.starttime = 0 #Physical starttime if any (0 is 1 Jan 1970 00:00:00)
160        #Checkpointing and storage
161        from config import default_datadir
162        self.datadir = default_datadir
163        self.filename = 'domain'
164        self.checkpoint = False
165
166    def __len__(self):
167        return self.number_of_elements
168
169    def build_vertexlist(self):
170        """Build vertexlist index by vertex ids and for each entry (point id)
171        build a list of (triangles, vertex_id) pairs that use the point
172        as vertex.
173
174        Preconditions:
175          self.coordinates and self.triangles are defined
176
177        Postcondition:
178          self.vertexlist is built
179        """
180        from Numeric import array
181
182        vertexlist = [None]*len(self.coordinates)
183        for i in range(self.number_of_elements):
184
185            #a = self.triangles[i, 0]
186            #b = self.triangles[i, 1]
187            #c = self.triangles[i, 2]
188            a = i
189            b = i + 1
190
191            #Register the vertices v as lists of
192            #(triangle_id, vertex_id) tuples associated with them
193            #This is used for smoothing
194            #for vertex_id, v in enumerate([a,b,c]):
195            for vertex_id, v in enumerate([a,b]):
196                if vertexlist[v] is None:
197                    vertexlist[v] = []
198
199                vertexlist[v].append( (i, vertex_id) )
200
201        self.vertexlist = vertexlist
202
203       
204    def build_neighbour_structure(self):
205        """Update all registered triangles to point to their neighbours.
206
207        Also, keep a tally of the number of boundaries for each triangle
208
209        Postconditions:
210          neighbours and neighbour_edges is populated
211          neighbours and neighbour_vertices is populated
212          number_of_boundaries integer array is defined.
213        """
214
215        #Step 1:
216        #Build dictionary mapping from segments (2-tuple of points)
217        #to left hand side edge (facing neighbouring triangle)
218
219        N = self.number_of_elements
220        neighbourdict = {}
221        #l_edge = 0
222        #r_edge = 1
223        l_vertex = 0
224        r_vertex = 1
225        for i in range(N):
226
227            #Register all segments as keys mapping to current triangle
228            #and segment id
229            #a = self.triangles[i, 0]
230            #b = self.triangles[i, 1]
231            #c = self.triangles[i, 2]
232            a = self.vertices[i,0]
233            b = self.vertices[i,1]
234           
235            """
236            if neighbourdict.has_key((a,b)):
237                    msg = "Edge 2 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[a,b][1],neighbourdict[a,b][0])
238                    raise msg
239            if neighbourdict.has_key((b,c)):
240                    msg = "Edge 0 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[b,c][1],neighbourdict[b,c][0])
241                    raise msg
242            if neighbourdict.has_key((c,a)):
243                    msg = "Edge 1 of triangle %d is duplicating edge %d of triangle %d.\n" %(i,neighbourdict[c,a][1],neighbourdict[c,a][0])
244                    raise msg
245                    """
246            #neighbourdict[a,b] = (i, 2) #(id, edge)
247            #neighbourdict[b,c] = (i, 0) #(id, edge)
248            #neighbourdict[c,a] = (i, 1) #(id, edge)
249            #neighbourdict[a,b] = (i, 1) #(id, edge)
250            #neighbourdict[b,a] = (i, 0) #(id, edge)
251            #neighbourdict[a,l_edge] = (i, 0) #(id, edge)
252            #neighbourdict[b,r_edge] = (i, 1) #(id, edge)
253            neighbourdict[a,l_vertex] = (i, 0) #(id, vertex)
254            neighbourdict[b,r_vertex] = (i, 1) #(id, vertex)
255
256
257        #Step 2:
258        #Go through triangles again, but this time
259        #reverse direction of segments and lookup neighbours.
260        for i in range(N):
261            #a = self.triangles[i, 0]
262            #b = self.triangles[i, 1]
263            #c = self.triangles[i, 2]
264           
265            a = self.vertices[i,0]
266            b = self.vertices[i,1]
267           
268            #self.number_of_boundaries[i] = 3
269            self.number_of_boundaries[i] = 2
270            #if neighbourdict.has_key((b,l_edge)):
271            if neighbourdict.has_key((b,l_vertex)):
272                #self.neighbours[i, 1] = neighbourdict[b,l_edge][0]
273                #self.neighbour_edges[i, 1] = neighbourdict[b,l_edge][1]
274                self.neighbours[i, 1] = neighbourdict[b,l_vertex][0]
275                self.neighbour_vertices[i, 1] = neighbourdict[b,l_vertex][1]
276                self.number_of_boundaries[i] -= 1
277
278            #if neighbourdict.has_key((a,r_edge)):
279            if neighbourdict.has_key((a,r_vertex)):
280                #self.neighbours[i, 0] = neighbourdict[a,r_edge][0]
281                #self.neighbour_edges[i, 0] = neighbourdict[a,r_edge][1]
282                self.neighbours[i, 0] = neighbourdict[a,r_vertex][0]
283                self.neighbour_vertices[i, 0] = neighbourdict[a,r_vertex][1]
284                self.number_of_boundaries[i] -= 1
285               
286            #if neighbourdict.has_key((b,a)):
287            #    self.neighbours[i, 1] = neighbourdict[b,a][0]
288            #    self.neighbour_edges[i, 1] = neighbourdict[b,a][1]
289            #    self.number_of_boundaries[i] -= 1
290
291            #if neighbourdict.has_key((c,b)):
292            #    self.neighbours[i, 0] = neighbourdict[c,b][0]
293            #    self.neighbour_edges[i, 0] = neighbourdict[c,b][1]
294            #    self.number_of_boundaries[i] -= 1
295
296            #if neighbourdict.has_key((a,b)):
297            #    self.neighbours[i, 0] = neighbourdict[a,b][0]
298            #    self.neighbour_edges[i, 0] = neighbourdict[a,b][1]
299            #    self.number_of_boundaries[i] -= 1
300
301    def build_surrogate_neighbour_structure(self):
302        """Build structure where each triangle edge points to its neighbours
303        if they exist. Otherwise point to the triangle itself.
304
305        The surrogate neighbour structure is useful for computing gradients
306        based on centroid values of neighbours.
307
308        Precondition: Neighbour structure is defined
309        Postcondition:
310          Surrogate neighbour structure is defined:
311          surrogate_neighbours: i0, i1, i2 where all i_k >= 0 point to
312          triangles.
313
314        """
315
316        N = self.number_of_elements
317        for i in range(N):
318            #Find all neighbouring volumes that are not boundaries
319            #for k in range(3):
320            for k in range(2):   
321                if self.neighbours[i, k] < 0:
322                    self.surrogate_neighbours[i, k] = i #Point this triangle
323                else:
324                    self.surrogate_neighbours[i, k] = self.neighbours[i, k]
325
326    def build_boundary_dictionary(self, boundary = None):
327        """Build or check the dictionary of boundary tags.
328         self.boundary is a dictionary of tags,
329         keyed by volume id and edge:
330         { (id, edge): tag, ... }
331
332         Postconditions:
333            self.boundary is defined.
334        """
335
336        from config import default_boundary_tag
337
338        if boundary is None:
339            boundary = {}
340            for vol_id in range(self.number_of_elements):
341                #for edge_id in range(0, 3):
342                #for edge_id in range(0, 2):
343                for vertex_id in range(0, 2):   
344                    #if self.neighbours[vol_id, edge_id] < 0:
345                    if self.neighbours[vol_id, vertex_id] < 0:   
346                        #boundary[(vol_id, edge_id)] = default_boundary_tag
347                        boundary[(vol_id, vertex_id)] = default_boundary_tag
348        else:
349            #Check that all keys in given boundary exist
350            #for vol_id, edge_id in boundary.keys():
351            for vol_id, vertex_id in boundary.keys():   
352                #msg = 'Segment (%d, %d) does not exist' %(vol_id, edge_id)
353                msg = 'Segment (%d, %d) does not exist' %(vol_id, vertex_id)
354                a, b = self.neighbours.shape
355                #assert vol_id < a and edge_id < b, msg
356                assert vol_id < a and vertex_id < b, msg
357
358                #FIXME: This assert violates internal boundaries (delete it)
359                #msg = 'Segment (%d, %d) is not a boundary' %(vol_id, edge_id)
360                #assert self.neighbours[vol_id, edge_id] < 0, msg
361
362            #Check that all boundary segments are assigned a tag
363            for vol_id in range(self.number_of_elements):
364                #for edge_id in range(0, 3):
365                #for edge_id in range(0, 2):
366                for vertex_id in range(0, 2):   
367                    #if self.neighbours[vol_id, edge_id] < 0:
368                    if self.neighbours[vol_id, vertex_id] < 0:   
369                        #if not boundary.has_key( (vol_id, edge_id) ):
370                        if not boundary.has_key( (vol_id, vertex_id) ):   
371                            msg = 'WARNING: Given boundary does not contain '
372                            #msg += 'tags for edge (%d, %d). '\
373                            #       %(vol_id, edge_id)
374                            msg += 'tags for vertex (%d, %d). '\
375                                   %(vol_id, vertex_id)
376                            msg += 'Assigning default tag (%s).'\
377                                   %default_boundary_tag
378
379                            #FIXME: Print only as per verbosity
380                            #print msg
381
382                            #FIXME: Make this situation an error in the future
383                            #and make another function which will
384                            #enable default boundary-tags where
385                            #tags a not specified
386                            #boundary[ (vol_id, edge_id) ] =\
387                            boundary[ (vol_id, vertex_id) ] =\
388                                      default_boundary_tag
389
390
391
392        self.boundary = boundary
393
394    def build_tagged_elements_dictionary(self, tagged_elements = None):
395        """Build the dictionary of element tags.
396         self.tagged_elements is a dictionary of element arrays,
397         keyed by tag:
398         { (tag): [e1, e2, e3..] }
399
400         Postconditions:
401            self.element_tag is defined
402        """
403        from Numeric import array, Int
404
405        if tagged_elements is None:
406            tagged_elements = {}
407        else:
408            #Check that all keys in given boundary exist
409            for tag in tagged_elements.keys():
410                tagged_elements[tag] = array(tagged_elements[tag]).astype(Int)
411
412                msg = 'Not all elements exist. '
413                assert max(tagged_elements[tag]) < self.number_of_elements, msg
414        #print "tagged_elements", tagged_elements
415        self.tagged_elements = tagged_elements
416
417    def get_boundary_tags(self):
418        """Return list of available boundary tags
419        """
420
421        tags = {}
422        for v in self.boundary.values():
423            tags[v] = 1
424
425        return tags.keys()
426       
427    def get_vertex_coordinates(self, obj = False):
428        """Return all vertex coordinates.
429        Return all vertex coordinates for all triangles as an Nx6 array
430        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
431
432        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
433        FIXME, we might make that the default.
434        FIXME Maybe use keyword: continuous = False for this condition?
435
436       
437        """
438
439        if obj is True:
440            from Numeric import concatenate, reshape
441            #V = self.vertex_coordinates
442            V = self.vertices
443            #return concatenate( (V[:,0:2], V[:,2:4], V[:,4:6]), axis=0)
444
445            N = V.shape[0]
446            #return reshape(V, (3*N, 2))
447            return reshape(V, (N, 2))
448        else:   
449            #return self.vertex_coordinates
450            return self.vertices
451   
452    def get_conserved_quantities(self, vol_id, vertex=None):#, edge=None):
453        """Get conserved quantities at volume vol_id
454
455        If vertex is specified use it as index for vertex values
456        If edge is specified use it as index for edge values
457        If neither are specified use centroid values
458        If both are specified an exeception is raised
459
460        Return value: Vector of length == number_of_conserved quantities
461
462        """
463
464        from Numeric import zeros, Float
465
466        #if not (vertex is None):# or edge is None):
467        #    msg = 'Values for both vertex and edge was specified.'
468        #    msg += 'Only one (or none) is allowed.'
469       #     raise msg
470
471        q = zeros( len(self.conserved_quantities), Float)
472
473        for i, name in enumerate(self.conserved_quantities):
474            Q = self.quantities[name]
475            if vertex is not None:
476                q[i] = Q.vertex_values[vol_id, vertex]
477            #elif edge is not None:
478            #    q[i] = Q.edge_values[vol_id, edge]
479            else:
480                q[i] = Q.centroid_values[vol_id]
481
482        return q
483       
484
485    def get_centroids(self):
486        """Return all coordinates of centroids
487        Return x coordinate of centroid for each element as a N array
488        """
489
490        return self.centroids
491
492    def get_vertices(self):
493        """Return all coordinates of centroids
494        Return x coordinate of centroid for each element as a N array
495        """
496
497        return self.vertices
498
499    def get_coordinate(self, elem_id, vertex=None):
500        """Return coordinate of centroid,
501        or left or right vertex.
502        Left vertex (vertex=0). Right vertex (vertex=1)
503        """
504
505        if vertex is None:
506            return self.centroids[elem_id]
507        else:
508            return self.vertices[elem_id,vertex]
509
510    def get_area(self, elem_id):
511        """Return area of element id
512        """
513
514        return self.areas[elem_id]
515
516    def get_quantity(self, name, location='vertices', indices = None):
517        """Get values for named quantity
518
519        name: Name of quantity
520
521        In case of location == 'centroids' the dimension values must
522        be a list of a Numerical array of length N, N being the number
523        of elements. Otherwise it must be of dimension Nx3.
524
525        Indices is the set of element ids that the operation applies to.
526
527        The values will be stored in elements following their
528        internal ordering.
529        """
530
531        return self.quantities[name].get_values( location, indices = indices)
532
533    def get_centroid_coordinates(self):
534        """Return all centroid coordinates.
535        Return all centroid coordinates for all triangles as an Nx2 array
536        (ordered as x0, y0 for each triangle)
537        """
538        return self.centroids
539
540    def set_quantity(self, name, *args, **kwargs):
541        """Set values for named quantity
542
543
544        One keyword argument is documented here:
545        expression = None, # Arbitrary expression
546
547        expression:
548          Arbitrary expression involving quantity names
549
550        See Quantity.set_values for further documentation.
551        """
552
553        #FIXME (Ole): Allow new quantities here
554        #from quantity import Quantity, Conserved_quantity
555        #Create appropriate quantity object
556        # #if name in self.conserved_quantities:
557        # #    self.quantities[name] = Conserved_quantity(self)
558        # #else:
559        # #    self.quantities[name] = Quantity(self)
560
561
562        #Do the expression stuff
563        if kwargs.has_key('expression'):
564            expression = kwargs['expression']
565            del kwargs['expression']
566
567            Q = self.create_quantity_from_expression(expression)
568            kwargs['quantity'] = Q
569       
570        #Assign values
571        self.quantities[name].set_values(*args, **kwargs)
572
573    def set_boundary(self, boundary_map):
574        """Associate boundary objects with tagged boundary segments.
575
576        Input boundary_map is a dictionary of boundary objects keyed
577        by symbolic tags to matched against tags in the internal dictionary
578        self.boundary.
579
580        As result one pointer to a boundary object is stored for each vertex
581        in the list self.boundary_objects.
582        More entries may point to the same boundary object
583
584        Schematically the mapping is from two dictionaries to one list
585        where the index is used as pointer to the boundary_values arrays
586        within each quantity.
587
588        self.boundary:          (vol_id, edge_id): tag
589        boundary_map (input):   tag: boundary_object
590        ----------------------------------------------
591        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
592
593
594        Pre-condition:
595          self.boundary has been built.
596
597        Post-condition:
598          self.boundary_objects is built
599
600        If a tag from the domain doesn't appear in the input dictionary an
601        exception is raised.
602        However, if a tag is not used to the domain, no error is thrown.
603        FIXME: This would lead to implementation of a
604        default boundary condition
605
606        Note: If a segment is listed in the boundary dictionary and if it is
607        not None, it *will* become a boundary -
608        even if there is a neighbouring triangle.
609        This would be the case for internal boundaries
610
611        Boundary objects that are None will be skipped.
612
613        FIXME: If set_boundary is called multiple times and if Boundary
614        object is changed into None, the neighbour structure will not be
615        restored!!!
616        """
617
618        self.boundary_objects = []
619        self.boundary_map = boundary_map  #Store for use with eg. boundary_stats.
620
621        #FIXME: Try to remove the sorting and fix test_mesh.py
622        x = self.boundary.keys()
623        x.sort()
624
625        #Loop through edges that lie on the boundary and associate them with
626        #callable boundary objects depending on their tags
627        #for k, (vol_id, edge_id) in enumerate(x):
628        for k, (vol_id, vertex_id) in enumerate(x):
629            #tag = self.boundary[ (vol_id, edge_id) ]
630            tag = self.boundary[ (vol_id, vertex_id) ]
631
632            if boundary_map.has_key(tag):
633                B = boundary_map[tag]  #Get callable boundary object
634
635                if B is not None:
636                    #self.boundary_objects.append( ((vol_id, edge_id), B) )
637                    #self.neighbours[vol_id, edge_id] = -len(self.boundary_objects)
638                    self.boundary_objects.append( ((vol_id, vertex_id), B) )
639                    self.neighbours[vol_id, vertex_id] = -len(self.boundary_objects)
640                else:
641                    pass
642                    #FIXME: Check and perhaps fix neighbour structure
643
644            else:
645                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
646                msg += 'bound to a boundary object.\n'
647                msg += 'All boundary tags defined in domain must appear '
648                msg += 'in the supplied dictionary.\n'
649                msg += 'The tags are: %s' %self.get_boundary_tags()
650                raise msg
651
652
653
654    def check_integrity(self):
655        #Mesh.check_integrity(self)
656       
657        for quantity in self.conserved_quantities:
658            msg = 'Conserved quantities must be a subset of all quantities'
659            assert quantity in self.quantities, msg
660
661        # #assert hasattr(self, 'boundary_objects')
662
663    def write_time(self):
664        print self.timestepping_statistics()
665
666    def timestepping_statistics(self):
667        """Return string with time stepping statistics for printing or logging
668        """
669
670        msg = ''
671        if self.min_timestep == self.max_timestep:
672            msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
673                   %(self.time, self.min_timestep, self.number_of_steps,
674                     self.number_of_first_order_steps)
675        elif self.min_timestep > self.max_timestep:
676            msg += 'Time = %.4f, steps=%d (%d)'\
677                   %(self.time, self.number_of_steps,
678                     self.number_of_first_order_steps)
679        else:
680            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
681                   %(self.time, self.min_timestep,
682                     self.max_timestep, self.number_of_steps,
683                     self.number_of_first_order_steps)
684
685        return msg
686
687    def get_name(self):
688        return self.filename
689
690    def set_name(self, name):
691        self.filename = name
692
693    def get_datadir(self):
694        return self.datadir
695
696    def set_datadir(self, name):
697        self.datadir = name
698
699
700    #--------------------------
701    # Main components of evolve
702    #--------------------------   
703
704    def evolve(self,
705               yieldstep = None,
706               finaltime = None,
707               duration = None,
708               skip_initial_step = False):
709        """Evolve model through time starting from self.starttime.
710
711
712        yieldstep: Interval between yields where results are stored,
713                   statistics written and domain inspected or
714                   possibly modified. If omitted the internal predefined
715                   max timestep is used.
716                   Internally, smaller timesteps may be taken.
717
718        duration: Duration of simulation
719
720        finaltime: Time where simulation should end. This is currently
721        relative time.  So it's the same as duration.
722
723        If both duration and finaltime are given an exception is thrown.
724
725
726        skip_initial_step: Boolean flag that decides whether the first
727        yield step is skipped or not. This is useful for example to avoid
728        duplicate steps when multiple evolve processes are dove tailed.
729
730
731        Evolve is implemented as a generator and is to be called as such, e.g.
732
733        for t in domain.evolve(yieldstep, finaltime):
734            <Do something with domain and t>
735
736
737        All times are given in seconds
738
739        """
740
741        from config import min_timestep, max_timestep, epsilon
742
743        # FIXME: Maybe lump into a larger check prior to evolving
744        msg = 'Boundary tags must be bound to boundary objects before '
745        msg += 'evolving system, '
746        msg += 'e.g. using the method set_boundary.\n'
747        msg += 'This system has the boundary tags %s '\
748               %self.get_boundary_tags()
749        assert hasattr(self, 'boundary_objects'), msg
750
751
752        if yieldstep is None:
753            yieldstep = max_timestep
754        else:
755            yieldstep = float(yieldstep)
756
757        self._order_ = self.default_order
758
759
760        if finaltime is not None and duration is not None:
761            # print 'F', finaltime, duration
762            msg = 'Only one of finaltime and duration may be specified'
763            raise msg
764        else:
765            if finaltime is not None:
766                self.finaltime = float(finaltime)
767            if duration is not None:
768                self.finaltime = self.starttime + float(duration)
769
770
771
772        N = len(self) # Number of triangles
773        self.yieldtime = 0.0 # Track time between 'yields'
774
775        # Initialise interval of timestep sizes (for reporting only)
776        self.min_timestep = max_timestep
777        self.max_timestep = min_timestep
778        self.number_of_steps = 0
779        self.number_of_first_order_steps = 0
780
781
782        # Update ghosts
783        self.update_ghosts()
784
785        # Initial update of vertex and edge values
786        self.distribute_to_vertices_and_edges()
787
788        # Update extrema if necessary (for reporting)
789        self.update_extrema()
790       
791        # Initial update boundary values
792        self.update_boundary()
793
794        # Or maybe restore from latest checkpoint
795        if self.checkpoint is True:
796            self.goto_latest_checkpoint()
797
798        if skip_initial_step is False:
799            yield(self.time)  # Yield initial values
800
801        while True:
802
803            # Evolve One Step, using appropriate timestepping method
804            if self.get_timestepping_method() == 'euler':
805                self.evolve_one_euler_step(yieldstep,finaltime)
806               
807            elif self.get_timestepping_method() == 'rk2':
808                self.evolve_one_rk2_step(yieldstep,finaltime)
809
810            elif self.get_timestepping_method() == 'rk3':
811                self.evolve_one_rk3_step(yieldstep,finaltime)               
812           
813            # Update extrema if necessary (for reporting)
814            self.update_extrema()           
815
816
817            self.yieldtime += self.timestep
818            self.number_of_steps += 1
819            if self._order_ == 1:
820                self.number_of_first_order_steps += 1
821
822            # Yield results
823            if finaltime is not None and self.time >= finaltime-epsilon:
824
825                if self.time > finaltime:
826                    # FIXME (Ole, 30 April 2006): Do we need this check?
827                    # Probably not (Ole, 18 September 2008). Now changed to
828                    # Exception
829                    msg = 'WARNING (domain.py): time overshot finaltime. '
830                    msg += 'Contact Ole.Nielsen@ga.gov.au'
831                    raise Exception, msg
832                   
833
834                # Yield final time and stop
835                self.time = finaltime
836                yield(self.time)
837                break
838
839
840            if self.yieldtime >= yieldstep:
841                # Yield (intermediate) time and allow inspection of domain
842
843                if self.checkpoint is True:
844                    self.store_checkpoint()
845                    self.delete_old_checkpoints()
846
847                # Pass control on to outer loop for more specific actions
848                yield(self.time)
849
850                # Reinitialise
851                self.yieldtime = 0.0
852                self.min_timestep = max_timestep
853                self.max_timestep = min_timestep
854                self.number_of_steps = 0
855                self.number_of_first_order_steps = 0
856                self.max_speed = zeros(N, Float)
857
858
859    def evolve_one_euler_step(self, yieldstep, finaltime):
860        """
861        One Euler Time Step
862        Q^{n+1} = E(h) Q^n
863        """
864
865        # Compute fluxes across each element edge
866        self.compute_fluxes()
867
868        # Update timestep to fit yieldstep and finaltime
869        self.update_timestep(yieldstep, finaltime)
870
871        # Update conserved quantities
872        self.update_conserved_quantities()
873
874        # Update ghosts
875        self.update_ghosts()
876
877        # Update vertex and edge values
878        self.distribute_to_vertices_and_edges()
879
880        # Update boundary values
881        self.update_boundary()
882
883        # Update time
884        self.time += self.timestep
885
886       
887
888
889    def evolve_one_rk2_step(self, yieldstep, finaltime):
890        """
891        One 2nd order RK timestep
892        Q^{n+1} = 0.5 Q^n + 0.5 E(h)^2 Q^n
893        """
894
895        # Save initial initial conserved quantities values
896        self.backup_conserved_quantities()           
897
898        #--------------------------------------
899        # First euler step
900        #--------------------------------------
901
902        # Compute fluxes across each element edge
903        self.compute_fluxes()
904
905        # Update timestep to fit yieldstep and finaltime
906        self.update_timestep(yieldstep, finaltime)
907
908        # Update conserved quantities
909        self.update_conserved_quantities()
910
911        # Update ghosts
912        self.update_ghosts()
913
914        # Update vertex and edge values
915        self.distribute_to_vertices_and_edges()
916
917        # Update boundary values
918        self.update_boundary()
919
920        # Update time
921        self.time += self.timestep
922
923        #------------------------------------
924        # Second Euler step
925        #------------------------------------
926           
927        # Compute fluxes across each element edge
928        self.compute_fluxes()
929
930        # Update conserved quantities
931        self.update_conserved_quantities()
932
933        #------------------------------------
934        # Combine initial and final values
935        # of conserved quantities and cleanup
936        #------------------------------------
937       
938        # Combine steps
939        self.saxpy_conserved_quantities(0.5, 0.5)
940
941        #-----------------------------------
942        # clean up vertex values
943        #-----------------------------------
944 
945        # Update ghosts
946        self.update_ghosts()
947
948        # Update vertex and edge values
949        self.distribute_to_vertices_and_edges()
950
951        # Update boundary values
952        self.update_boundary()
953
954
955
956    def evolve_one_rk3_step(self, yieldstep, finaltime):
957        """
958        One 3rd order RK timestep
959        Q^(1) = 3/4 Q^n + 1/4 E(h)^2 Q^n  (at time t^n + h/2)
960        Q^{n+1} = 1/3 Q^n + 2/3 E(h) Q^(1) (at time t^{n+1})
961        """
962
963        # Save initial initial conserved quantities values
964        self.backup_conserved_quantities()           
965
966        initial_time = self.time
967       
968        #--------------------------------------
969        # First euler step
970        #--------------------------------------
971
972        # Compute fluxes across each element edge
973        self.compute_fluxes()
974
975        # Update timestep to fit yieldstep and finaltime
976        self.update_timestep(yieldstep, finaltime)
977
978        # Update conserved quantities
979        self.update_conserved_quantities()
980
981        # Update ghosts
982        self.update_ghosts()
983
984        # Update vertex and edge values
985        self.distribute_to_vertices_and_edges()
986
987        # Update boundary values
988        self.update_boundary()
989
990        # Update time
991        self.time += self.timestep
992
993        #------------------------------------
994        # Second Euler step
995        #------------------------------------
996           
997        # Compute fluxes across each element edge
998        self.compute_fluxes()
999
1000        # Update conserved quantities
1001        self.update_conserved_quantities()
1002
1003        #------------------------------------
1004        #Combine steps to obtain intermediate
1005        #solution at time t^n + 0.5 h
1006        #------------------------------------
1007
1008        # Combine steps
1009        self.saxpy_conserved_quantities(0.25, 0.75)
1010 
1011        # Update ghosts
1012        self.update_ghosts()
1013
1014        # Update vertex and edge values
1015        self.distribute_to_vertices_and_edges()
1016
1017        # Update boundary values
1018        self.update_boundary()
1019
1020        # Set substep time
1021        self.time = initial_time + self.timestep*0.5
1022
1023        #------------------------------------
1024        # Third Euler step
1025        #------------------------------------
1026           
1027        # Compute fluxes across each element edge
1028        self.compute_fluxes()
1029
1030        # Update conserved quantities
1031        self.update_conserved_quantities()
1032
1033        #------------------------------------
1034        # Combine final and initial values
1035        # and cleanup
1036        #------------------------------------
1037       
1038        # Combine steps
1039        self.saxpy_conserved_quantities(2.0/3.0, 1.0/3.0)
1040 
1041        # Update ghosts
1042        self.update_ghosts()
1043
1044        # Update vertex and edge values
1045        self.distribute_to_vertices_and_edges()
1046
1047        # Update boundary values
1048        self.update_boundary()
1049
1050        # Set new time
1051        self.time = initial_time + self.timestep       
1052       
1053
1054    def backup_conserved_quantities(self):
1055        N = len(self) # Number_of_triangles
1056
1057        # Backup conserved_quantities centroid values
1058        for name in self.conserved_quantities:
1059            Q = self.quantities[name]
1060            Q.backup_centroid_values()       
1061
1062    def saxpy_conserved_quantities(self,a,b):
1063        N = len(self) #number_of_triangles
1064
1065        # Backup conserved_quantities centroid values
1066        for name in self.conserved_quantities:
1067            Q = self.quantities[name]
1068            Q.saxpy_centroid_values(a,b)       
1069
1070
1071    #==============================
1072    # John Jakeman's old evolve code
1073    #=============================
1074
1075    def evolve_john(self, yieldstep = None, finaltime = None,
1076               skip_initial_step = False):
1077        """Evolve model from time=0.0 to finaltime yielding results
1078        every yieldstep.
1079
1080        Internally, smaller timesteps may be taken.
1081
1082        Evolve is implemented as a generator and is to be called as such, e.g.
1083
1084        for t in domain.evolve(timestep, yieldstep, finaltime):
1085            <Do something with domain and t>
1086
1087        """
1088
1089        from config import min_timestep, max_timestep, epsilon
1090
1091        #FIXME: Maybe lump into a larger check prior to evolving
1092        msg = 'Boundary tags must be bound to boundary objects before evolving system, '
1093        msg += 'e.g. using the method set_boundary.\n'
1094        msg += 'This system has the boundary tags %s ' %self.get_boundary_tags()
1095        assert hasattr(self, 'boundary_objects'), msg
1096
1097        # #self.set_defaults()
1098
1099        if yieldstep is None:
1100            yieldstep = max_timestep
1101        else:
1102            yieldstep = float(yieldstep)
1103
1104        self.order = self.default_order
1105       
1106        self.time_order = self.default_time_order
1107
1108        self.yieldtime = 0.0 #Time between 'yields'
1109
1110        #Initialise interval of timestep sizes (for reporting only)
1111        # SEEMS WIERD
1112        self.min_timestep = max_timestep
1113        self.max_timestep = min_timestep
1114        self.finaltime = finaltime
1115        self.number_of_steps = 0
1116        self.number_of_first_order_steps = 0
1117       
1118        #update ghosts
1119        self.update_ghosts()
1120   
1121        #Initial update of vertex and edge values
1122        self.distribute_to_vertices_and_edges()
1123
1124        #Initial update boundary values
1125        self.update_boundary()
1126
1127        #Or maybe restore from latest checkpoint
1128        if self.checkpoint is True:
1129            self.goto_latest_checkpoint()
1130
1131        if skip_initial_step is False:
1132            yield(self.time)  #Yield initial values   
1133
1134        while True:
1135            if self.time_order == 1:
1136                #Compute fluxes across each element edge
1137                self.compute_fluxes()
1138                #Update timestep to fit yieldstep and finaltime
1139                self.update_timestep(yieldstep, finaltime)
1140                #Compute forcing terms
1141                self.compute_forcing_terms()
1142                #Update conserved quantities
1143                self.update_conserved_quantities(self.timestep)
1144                #update ghosts
1145                #self.update_ghosts()
1146                #Update vertex and edge values
1147                self.distribute_to_vertices_and_edges()               
1148                #Update boundary values
1149                self.update_boundary()
1150
1151            elif self.time_order == 2:
1152               
1153                self.compute_timestep() #self.compute_fluxes() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1154
1155                #Solve inhomogeneous operator for half a timestep
1156                self.solve_inhomogenous_second_order(yieldstep, finaltime)
1157               
1158                #Solve homogeneous operator for full timestep using
1159                #Harten second order timestepping
1160                self.solve_homogenous_second_order(yieldstep,finaltime)
1161
1162                #Solve inhomogeneous operator for half a timestep
1163                self.solve_inhomogenous_second_order(yieldstep, finaltime)
1164               
1165            #Update time
1166            self.time += self.timestep
1167            self.yieldtime += self.timestep
1168            self.number_of_steps += 1
1169            if self.order == 1:
1170                self.number_of_first_order_steps += 1
1171
1172            #Yield results
1173            if finaltime is not None and abs(self.time - finaltime) < epsilon:
1174
1175                #FIXME: There is a rare situation where the
1176                #final time step is stored twice. Can we make a test?
1177
1178                # Yield final time and stop
1179                yield(self.time)
1180                break
1181
1182
1183            if abs(self.yieldtime - yieldstep) < epsilon:
1184                # Yield (intermediate) time and allow inspection of domain
1185
1186                if self.checkpoint is True:
1187                    self.store_checkpoint()
1188                    self.delete_old_checkpoints()
1189
1190                #Pass control on to outer loop for more specific actions
1191                yield(self.time)
1192
1193                # Reinitialise
1194                self.yieldtime = 0.0
1195                self.min_timestep = max_timestep
1196                self.max_timestep = min_timestep
1197                self.number_of_steps = 0
1198                self.number_of_first_order_steps = 0
1199
1200    def solve_inhomogenous_second_order(self,yieldstep, finaltime):
1201
1202        #Update timestep to fit yieldstep and finaltime
1203        self.update_timestep(yieldstep, finaltime)
1204        #Compute forcing terms
1205        self.compute_forcing_terms()
1206        #Update conserved quantities
1207        self.update_conserved_quantities(0.5*self.timestep)
1208        #Update vertex and edge values
1209        self.distribute_to_vertices_and_edges()
1210        #Update boundary values
1211        self.update_boundary()
1212
1213    def  solve_homogenous_second_order(self,yieldstep,finaltime):
1214        """Use Shu Second order timestepping to update
1215        conserved quantities
1216
1217        q^{n+1/2} = q^{n}+0.5*dt*F^{n}
1218        q^{n+1} = q^{n}+dt*F^{n+1/2}
1219        """
1220        import copy
1221        from Numeric import zeros,Float
1222
1223        N = self.number_of_elements
1224
1225        self.compute_fluxes()
1226        #Update timestep to fit yieldstep and finaltime
1227        self.update_timestep(yieldstep, finaltime)
1228        #Compute forcing terms
1229        #NOT NEEDED FOR 2ND ORDER STRANG SPLIITING
1230        #ADDING THIS WILL NEED TO REMOVE ZEROING IN COMPUTE_FORCING
1231        #self.compute_forcing_terms()
1232
1233        QC = zeros((N,len(self.conserved_quantities)),Float)
1234        QF = zeros((N,len(self.conserved_quantities)),Float)
1235
1236        i = 0
1237        for name in self.conserved_quantities:
1238            Q = self.quantities[name]
1239            #Store the centroid values at time t^n
1240            QC[:,i] = copy.copy(Q.centroid_values)
1241            QF[:,i] = copy.copy(Q.explicit_update)
1242            #Update conserved quantities
1243            Q.update(self.timestep)
1244            i+=1
1245           
1246        #Update vertex and edge values
1247        self.distribute_to_vertices_and_edges()
1248        #Update boundary values
1249        self.update_boundary()
1250       
1251        self.compute_fluxes()
1252        self.update_timestep(yieldstep, finaltime)
1253        #Compute forcing terms
1254        #NOT NEEDED FOR 2ND ORDER STRANG SPLIITING
1255        #self.compute_forcing_terms()
1256
1257        i = 0
1258        for name in self.conserved_quantities:
1259            Q = self.quantities[name]
1260            Q.centroid_values = QC[:,i]
1261            Q.explicit_update = 0.5*(Q.explicit_update+QF[:,i]) 
1262            #Update conserved quantities
1263            Q.update(self.timestep)
1264            i+=1
1265       
1266        #Update vertex and edge values
1267        self.distribute_to_vertices_and_edges()
1268        #Update boundary values
1269        self.update_boundary()
1270
1271    def  solve_homogenous_second_order_harten(self,yieldstep,finaltime):
1272        """Use Harten Second order timestepping to update
1273        conserved quantities
1274
1275        q^{n+1/2} = q^{n}+0.5*dt*F^{n}
1276        q^{n+1} = q^{n}+dt*F^{n+1/2}
1277        """
1278        import copy
1279        from Numeric import zeros,Float
1280
1281        N = self.number_of_elements
1282
1283        self.compute_fluxes()
1284        #Update timestep to fit yieldstep and finaltime
1285        self.update_timestep(yieldstep, finaltime)
1286        #Compute forcing terms
1287        #NOT NEEDED FOR 2ND ORDER STRANG SPLIITING
1288        #ADDING THIS WILL NEED TO REMOVE ZEROING IN COMPUTE_FORCING
1289        #self.compute_forcing_terms()
1290
1291        QC = zeros((N,len(self.conserved_quantities)),Float)
1292
1293        i = 0
1294        for name in self.conserved_quantities:
1295            Q = self.quantities[name]
1296            #Store the centroid values at time t^n
1297            QC[:,i] = copy.copy(Q.centroid_values)
1298            #Update conserved quantities
1299            Q.update(0.5*self.timestep)
1300            i+=1
1301           
1302        #Update vertex and edge values
1303        self.distribute_to_vertices_and_edges()
1304        #Update boundary values
1305        self.update_boundary()
1306       
1307        self.compute_fluxes()
1308        self.update_timestep(yieldstep, finaltime)
1309        #Compute forcing terms
1310        #NOT NEEDED FOR 2ND ORDER STRANG SPLIITING
1311        #self.compute_forcing_terms()
1312
1313        i = 0
1314        for name in self.conserved_quantities:
1315            Q = self.quantities[name]
1316            Q.centroid_values = QC[:,i] 
1317            #Update conserved quantities
1318            Q.update(self.timestep)
1319            i+=1
1320       
1321        #Update vertex and edge values
1322        self.distribute_to_vertices_and_edges()
1323        #Update boundary values
1324        self.update_boundary()
1325       
1326    def distribute_to_vertices_and_edges(self):
1327        """Extrapolate conserved quantities from centroid to
1328        vertices and edge-midpoints for each volume
1329
1330        Default implementation is straight first order,
1331        i.e. constant values throughout each element and
1332        no reference to non-conserved quantities.
1333        """
1334
1335        for name in self.conserved_quantities:
1336            Q = self.quantities[name]
1337            if self.order == 1:
1338                Q.extrapolate_first_order()
1339            elif self.order == 2:
1340                Q.extrapolate_second_order()
1341                #Q.limit()
1342            else:
1343                raise 'Unknown order'
1344            #Q.interpolate_from_vertices_to_edges()
1345
1346
1347    def update_ghosts(self):
1348        pass
1349   
1350    def update_boundary(self):
1351        """Go through list of boundary objects and update boundary values
1352        for all conserved quantities on boundary.
1353        """
1354
1355        #FIXME: Update only those that change (if that can be worked out)
1356        #FIXME: Boundary objects should not include ghost nodes.
1357        #for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
1358        #    q = B.evaluate(vol_id, edge_id)
1359        for i, ((vol_id, vertex_id), B) in enumerate(self.boundary_objects):
1360            q = B.evaluate(vol_id, vertex_id)
1361
1362            for j, name in enumerate(self.conserved_quantities):
1363                Q = self.quantities[name]
1364                Q.boundary_values[i] = q[j]
1365   
1366    def update_timestep(self, yieldstep, finaltime):
1367
1368        from config import min_timestep
1369
1370        # self.timestep is calculated from speed of characteristics
1371        # Apply CFL condition here
1372        timestep = self.CFL*self.timestep
1373
1374        #Record maximal and minimal values of timestep for reporting
1375        self.max_timestep = max(timestep, self.max_timestep)
1376        self.min_timestep = min(timestep, self.min_timestep)
1377
1378        #Protect against degenerate time steps
1379        if timestep < min_timestep:
1380
1381            #Number of consecutive small steps taken b4 taking action
1382            self.smallsteps += 1
1383
1384            if self.smallsteps > self.max_smallsteps:
1385                self.smallsteps = 0 #Reset
1386
1387                if self.order == 1:
1388                    msg = 'WARNING: Too small timestep %.16f reached '\
1389                          %timestep
1390                    msg += 'even after %d steps of 1 order scheme'\
1391                           %self.max_smallsteps
1392                    print msg
1393                    timestep = min_timestep  #Try enforcing min_step
1394
1395                    #raise msg
1396                else:
1397                    #Try to overcome situation by switching to 1 order
1398                    print "changing Order 1"
1399                    self.order = 1
1400
1401        else:
1402            self.smallsteps = 0
1403            if self.order == 1 and self.default_order == 2:
1404                self.order = 2
1405
1406
1407        #Ensure that final time is not exceeded
1408        if finaltime is not None and self.time + timestep > finaltime:
1409            timestep = finaltime-self.time
1410
1411        #Ensure that model time is aligned with yieldsteps
1412        if self.yieldtime + timestep > yieldstep:
1413            timestep = yieldstep-self.yieldtime
1414
1415        self.timestep = timestep
1416
1417    def update_extrema(self):
1418        pass
1419
1420    def compute_forcing_terms(self):
1421        """If there are any forcing functions driving the system
1422        they should be defined in Domain subclass and appended to
1423        the list self.forcing_terms
1424        """
1425        #Clears explicit_update needed for second order method
1426        if self.time_order == 2:
1427            for name in self.conserved_quantities:
1428                Q = self.quantities[name]
1429                Q.explicit_update[:] = 0.0
1430
1431        for f in self.forcing_terms:
1432            f(self)
1433           
1434
1435    def update_derived_quantites(self):
1436        pass
1437   
1438    #def update_conserved_quantities(self):
1439    def update_conserved_quantities(self,timestep):
1440        """Update vectors of conserved quantities using previously
1441        computed fluxes specified forcing functions.
1442        """
1443
1444        from Numeric import ones, sum, equal, Float
1445
1446        N = self.number_of_elements
1447        d = len(self.conserved_quantities)
1448
1449        #timestep = self.timestep
1450
1451        #Compute forcing terms
1452        #self.compute_forcing_terms()
1453
1454        #Update conserved_quantities
1455        for name in self.conserved_quantities:
1456            Q = self.quantities[name]
1457            Q.update(timestep)
1458
1459            #Clean up
1460            #Note that Q.explicit_update is reset by compute_fluxes
1461
1462            #MH090605 commented out the following since semi_implicit_update is now re-initialized
1463            #at the end of the _update function in quantity_ext.c (This is called by the
1464            #preceeding Q.update(timestep) statement above).
1465            #For run_profile.py with N=128, the time of update_conserved_quantities is cut from 14.00 secs
1466            #to 8.35 secs
1467
1468            #Q.semi_implicit_update[:] = 0.0
1469
1470if __name__ == "__main__":
1471
1472    points1 = [0.0, 1.0, 2.0, 3.0]
1473    D1 = Domain(points1)
1474
1475    print D1.get_coordinate(0)
1476    print D1.get_coordinate(0,1)
1477    print 'Number of Elements = ',D1.number_of_elements
1478
1479    try:
1480        print D1.get_coordinate(3)
1481    except:
1482        pass
1483    else:
1484        msg =  'Should have raised an out of bounds exception'
1485        raise msg
1486
1487    #points2 = [0.0, 1.0, 2.0, 3.0, 2.5]
1488    #D2 = Domain(points2)
Note: See TracBrowser for help on using the repository browser.