source: inundation/ga/storm_surge/pyvolution/domain.py @ 761

Last change on this file since 761 was 750, checked in by ole, 20 years ago

Test that initial values are indeed output at t == 0 (just to make sure).
Also a check that bounsry is created before starting evolve.

File size: 18.2 KB
Line 
1"""Class Domain - 2D triangular 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"""
9
10from mesh import Mesh
11from generic_boundary_conditions import *
12import types
13
14class Domain(Mesh):
15
16    def __init__(self, coordinates, vertices, boundary = None,
17                 conserved_quantities = None, other_quantities = None,
18                 tagged_elements = None):
19
20        Mesh.__init__(self, coordinates, vertices, boundary, tagged_elements)
21
22        from Numeric import zeros, Float
23        from quantity import Quantity, Conserved_quantity
24
25        #List of quantity names entering
26        #the conservation equations
27        #(Must be a subset of quantities)
28        if conserved_quantities is None:           
29            self.conserved_quantities = []
30        else:
31            self.conserved_quantities = conserved_quantities           
32
33        if other_quantities is None:
34            self.other_quantities = []
35        else:
36            self.other_quantities = other_quantities           
37           
38
39        #Build dictionary of Quantity instances keyed by quantity names
40        self.quantities = {}
41
42        #FIXME: remove later
43        for name in self.conserved_quantities:
44            self.quantities[name] = Conserved_quantity(self)
45        for name in self.other_quantities:
46            self.quantities[name] = Quantity(self)
47
48        #Create an empty list for explicit forcing terms
49        self.forcing_terms = []
50
51
52
53        #Defaults
54        from config import max_smallsteps, beta, epsilon
55        self.beta = beta
56        self.epsilon = epsilon
57        self.default_order = 1
58        self.order = self.default_order
59        self.smallsteps = 0
60        self.max_smallsteps = max_smallsteps       
61        self.number_of_steps = 0
62        self.number_of_first_order_steps = 0       
63
64        #Model time   
65        self.time = 0.0
66        self.finaltime = None
67        self.min_timestep = self.max_timestep = 0.0
68        self.starttime = None
69       
70        #Checkpointing         
71        self.filename = 'domain'
72        self.checkpoint = False
73       
74
75    #Public interface to Domain       
76    def get_conserved_quantities(self, vol_id, vertex=None, edge=None):
77        """Get conserved quantities at volume vol_id
78
79        If vertex is specified use it as index for vertex values
80        If edge is specified use it as index for edge values
81        If neither are specified use centroid values
82        If both are specified an exeception is raised
83
84        Return value: Vector of length == number_of_conserved quantities
85       
86        """
87
88        from Numeric import zeros, Float
89       
90        if not (vertex is None or edge is None):
91            msg = 'Values for both vertex and edge was specified.'
92            msg += 'Only one (or none) is allowed.' 
93            raise msg
94
95        q = zeros( len(self.conserved_quantities), Float)
96       
97        for i, name in enumerate(self.conserved_quantities):
98            Q = self.quantities[name]
99            if vertex is not None:
100                q[i] = Q.vertex_values[vol_id, vertex]
101            elif edge is not None:
102                q[i] = Q.edge_values[vol_id, edge]
103            else:
104                q[i] = Q.centroid_values[vol_id]               
105
106        return q
107
108
109    def set_quantity_vertices_dict(self, quantity_dict):
110        """Set values for named quantities.
111        The index is the quantity
112       
113        name: Name of quantity
114        X: Compatible list, Numeric array, const or function (see below)
115       
116        The values will be stored in elements following their
117        internal ordering.
118
119        """
120        for key in quantity_dict.keys():
121            self.set_quantity(key, quantity_dict[key], location='vertices')
122       
123
124    def set_quantity(self, name, X, location='vertices', indexes = None):
125        """Set values for named quantity
126       
127        name: Name of quantity
128        X: Compatible list, Numeric array, const or function (see below)
129        location: Where values are to be stored.
130                  Permissible options are: vertices, edges, centroid
131
132        In case of location == 'centroid' the dimension values must
133        be a list of a Numerical array of length N, N being the number
134        of elements. Otherwise it must be of dimension Nx3.
135       
136        Indexes is the set of element ids that the operation applies to
137       
138        The values will be stored in elements following their
139        internal ordering.
140        """
141
142        #from quantity import Quantity, Conserved_quantity
143       
144        #Create appropriate quantity object
145        ##if name in self.conserved_quantities:
146        ##    self.quantities[name] = Conserved_quantity(self)
147        ##else:
148        ##    self.quantities[name] = Quantity(self)           
149
150        #Set value
151        self.quantities[name].set_values(X, location, indexes = indexes)
152
153         
154    def get_quantity(self, name, location='vertices', indexes = None):
155        """Get values for named quantity
156       
157        name: Name of quantity
158       
159        In case of location == 'centroid' the dimension values must
160        be a list of a Numerical array of length N, N being the number
161        of elements. Otherwise it must be of dimension Nx3.
162
163        Indexes is the set of element ids that the operation applies to.
164       
165        The values will be stored in elements following their
166        internal ordering.
167        """
168
169        return self.quantities[name].get_values( location, indexes = indexes)
170   
171
172    def set_boundary(self, boundary_map):
173        """Associate boundary objects with tagged boundary segments.
174
175        Input boundary_map is a dictionary of boundary objects keyed
176        by symbolic tags to matched against tags in the internal dictionary
177        self.boundary.
178
179        As result one pointer to a boundary object is stored for each vertex
180        in the list self.boundary_objects.
181        More entries may point to the same boundary object
182       
183        Schematically the mapping is from two dictionaries to one list
184        where the index is used as pointer to the boundary_values arrays
185        within each quantity.
186
187        self.boundary:          (vol_id, edge_id): tag
188        boundary_map (input):   tag: boundary_object
189        ----------------------------------------------
190        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
191       
192
193        Pre-condition:
194          self.boundary has been built.
195
196        Post-condition:
197          self.boundary_objects is built
198
199        If a tag from the domain doesn't appear in the input dictionary an
200        exception is raised.
201        However, if a tag is not used to the domain, no error is thrown.
202        FIXME: This would lead to implementation of a
203        default boundary condition       
204
205        Note: If a segment is listed in the boundary dictionary and if it is
206        not None, it *will* become a boundary -
207        even if there is a neighbouring triangle.
208        This would be the case for internal boundaries
209       
210        Boundary objects that are None will be skipped.
211
212        FIXME: If set_boundary is called multiple times and if Boundary
213        object is changed into None, the neighbour structure will not be
214        restored!!!
215       
216
217        """
218
219        self.boundary_objects = []
220
221        #FIXME: Try to remove the sorting and fix test_mesh.py
222        x = self.boundary.keys()
223        x.sort()
224        for k, (vol_id, edge_id) in enumerate(x):
225            tag = self.boundary[ (vol_id, edge_id) ]
226
227            if boundary_map.has_key(tag):
228                B = boundary_map[tag]
229
230                if B is not None:
231                    self.boundary_objects.append( ((vol_id, edge_id), B) )
232                    self.neighbours[vol_id, edge_id] = -len(self.boundary_objects)
233                else:
234                    pass
235                    #FIXME: Check and perhaps fix neighbour structure
236                   
237
238            else:
239                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
240                msg += 'bound to a boundary object.\n'
241                msg += 'All boundary tags defined in domain must appear '
242                msg += 'in the supplied dictionary.\n'
243                msg += 'The tags are: %s' %self.get_boundary_tags()
244                raise msg
245
246    def set_region(self, functions):
247        # The order of functions in the list is used.
248        if type(functions) not in [types.ListType,types.TupleType]:
249            functions = [functions]
250        for function in functions:
251            for tag in self.tagged_elements.keys():
252                function(tag, self.tagged_elements[tag], self)
253
254                #Do we need to do this sort of thing?
255                #self = function(tag, self.tagged_elements[tag], self)
256           
257    #MISC       
258    def check_integrity(self):
259        Mesh.check_integrity(self)
260
261        for quantity in self.conserved_quantities:
262            msg = 'Conserved quantities must be a subset of all quantities'
263            assert quantity in self.quantities, msg
264
265        ##assert hasattr(self, 'boundary_objects')   
266   
267    def write_time(self):
268        if self.min_timestep == self.max_timestep:
269            print 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
270                  %(self.time, self.min_timestep, self.number_of_steps,
271                    self.number_of_first_order_steps)
272        elif self.min_timestep > self.max_timestep:
273            print 'Time = %.4f, steps=%d (%d)'\
274                  %(self.time, self.number_of_steps,
275                    self.number_of_first_order_steps)           
276        else:
277            print 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
278                  %(self.time, self.min_timestep,
279                    self.max_timestep, self.number_of_steps,
280                    self.number_of_first_order_steps)
281
282
283    def get_name(self):
284        return self.filename   
285   
286    def set_name(self, name):
287        self.filename = name   
288
289    #def set_defaults(self):
290    #    """Set default values for uninitialised quantities.
291    #    Should be overridden or specialised by specific modules
292    #    """#
293    #
294    #    for name in self.conserved_quantities + self.other_quantities:
295    #        self.set_quantity(name, 0.0)       
296               
297
298    ###########################
299    #Main components of evolve
300
301    def evolve(self, yieldstep = None, finaltime = None):
302        """Evolve model from time=0.0 to finaltime yielding results
303        every yieldstep.
304       
305        Internally, smaller timesteps may be taken.
306
307        Evolve is implemented as a generator and is to be called as such, e.g.
308       
309        for t in domain.evolve(timestep, yieldstep, finaltime):
310            <Do something with domain and t>
311       
312        """
313
314        #import data_manager
315        from config import min_timestep, max_timestep, epsilon
316
317        #FIXME: Maybe lump into a larger check prior to evolving
318        msg = 'Boundary tags must be bound to boundary objects before evolving system, '
319        msg += 'e.g. using the method set_boundary.\n'       
320        msg += 'This system has the boundary tags %s ' %self.get_boundary_tags()
321        assert hasattr(self, 'boundary_objects'), msg
322       
323        ##self.set_defaults()
324
325        if yieldstep is None:
326            yieldstep = max_timestep
327
328        self.order = self.default_order
329       
330           
331        self.yieldtime = 0.0 #Time between 'yields'
332
333        #Initialise interval of timestep sizes (for reporting only)
334        self.min_timestep = max_timestep
335        self.max_timestep = min_timestep
336        self.finaltime = finaltime
337        self.number_of_steps = 0
338        self.number_of_first_order_steps = 0               
339
340        #Initial update of vertex and edge values
341        self.distribute_to_vertices_and_edges()
342       
343       
344        #Or maybe restore from latest checkpoint
345        if self.checkpoint is True:
346            self.goto_latest_checkpoint()
347
348           
349        yield(self.time)  #Yield initial values
350       
351        while True:
352            #Update boundary values
353            self.update_boundary()           
354
355            #Compute fluxes across each element edge
356            self.compute_fluxes()
357
358            #Update timestep to fit yieldstep and finaltime
359            self.update_timestep(yieldstep, finaltime)
360
361            #Update conserved quantities
362            self.update_conserved_quantities()           
363
364            #Update vertex and edge values
365            self.distribute_to_vertices_and_edges()
366           
367            #Update time   
368            self.time += self.timestep
369            self.yieldtime += self.timestep
370            self.number_of_steps += 1
371            if self.order == 1:
372                self.number_of_first_order_steps += 1       
373
374            #Yield results
375            if finaltime is not None and abs(self.time - finaltime) < epsilon:
376                # Yield final time and stop
377                yield(self.time)
378                break
379
380               
381            if abs(self.yieldtime - yieldstep) < epsilon: 
382                # Yield (intermediate) time and allow inspection of domain
383
384                if self.checkpoint is True:
385                    self.store_checkpoint()
386                    self.delete_old_checkpoints()
387                   
388                #Pass control on to outer loop for more specific actions   
389                yield(self.time) 
390
391                # Reinitialise
392                self.yieldtime = 0.0               
393                self.min_timestep = max_timestep
394                self.max_timestep = min_timestep
395                self.number_of_steps = 0
396                self.number_of_first_order_steps = 0                       
397
398           
399    def evolve_to_end(self, finaltime = 1.0):
400        """Iterate evolve all the way to the end
401        """
402
403        for _ in self.evolve(yieldstep=None, finaltime=finaltime):           
404            pass
405       
406
407   
408    def update_boundary(self):
409        """Go through list of boundary objects and update boundary values
410        for all conserved quantities on boundary.
411        """
412
413        #FIXME: Update only those that change (if that can be worked out)
414        for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
415            q = B.evaluate(vol_id, edge_id)
416
417            for j, name in enumerate(self.conserved_quantities):
418                Q = self.quantities[name]
419                Q.boundary_values[i] = q[j]
420
421
422    def compute_fluxes(self):
423        msg = 'Method compute_fluxes must be overridden by Domain subclass'
424        raise msg
425
426
427    def update_timestep(self, yieldstep, finaltime):
428
429        from config import min_timestep
430   
431        timestep = self.timestep
432   
433        #Record maximal and minimal values of timestep for reporting   
434        self.max_timestep = max(timestep, self.max_timestep)
435        self.min_timestep = min(timestep, self.min_timestep)
436   
437        #Protect against degenerate time steps
438        if timestep < min_timestep:
439
440            #Number of consecutive small steps taken b4 taking action
441            self.smallsteps += 1 
442
443            if self.smallsteps > self.max_smallsteps:
444                self.smallsteps = 0 #Reset
445               
446                if self.order == 1:
447                    msg = 'Too small timestep %.16f reached ' %timestep
448                    msg += 'even after %d steps of 1 order scheme'\
449                           %self.max_smallsteps
450
451                    raise msg
452                else:
453                    #Try to overcome situation by switching to 1 order
454                    self.order = 1
455
456        else:
457            self.smallsteps = 0
458            if self.order == 1 and self.default_order == 2:
459                self.order = 2
460                   
461
462        #Ensure that final time is not exceeded
463        if finaltime is not None and self.time + timestep > finaltime:
464            timestep = finaltime-self.time
465
466        #Ensure that model time is aligned with yieldsteps
467        if self.yieldtime + timestep > yieldstep:
468            timestep = yieldstep-self.yieldtime
469
470        self.timestep = timestep
471
472
473
474    def compute_forcing_terms(self):         
475        """If there are any forcing functions driving the system
476        they should be defined in Domain subclass and appended to
477        the list self.forcing_terms
478        """
479
480        for f in self.forcing_terms:
481            f(self)
482
483       
484
485    def update_conserved_quantities(self):
486        """Update vectors of conserved quantities using previously
487        computed fluxes specified forcing functions.
488        """
489
490        from Numeric import ones, sum, equal, Float
491           
492        N = self.number_of_elements
493        d = len(self.conserved_quantities)
494       
495        timestep = self.timestep
496
497        #Compute forcing terms   
498        self.compute_forcing_terms()   
499
500        #Update conserved_quantities
501        for name in self.conserved_quantities:
502            Q = self.quantities[name]
503            Q.update(timestep)
504
505            #Clean up
506            #Note that Q.explicit_update is reset by compute_fluxes           
507            Q.semi_implicit_update[:] = 0.0
508
509           
510
511    def distribute_to_vertices_and_edges(self):
512        """Extrapolate conserved quantities from centroid to
513        vertices and edge-midpoints for each volume
514
515        Default implementation is straight first order,
516        i.e. constant values throughout each element and
517        no reference to non-conserved quantities.
518        """
519
520        for name in self.conserved_quantities:
521            Q = self.quantities[name]
522            if self.order == 1:
523                Q.extrapolate_first_order()
524            elif self.order == 2:
525                Q.extrapolate_second_order()
526                Q.limit()                               
527            else:
528                raise 'Unknown order'
529            Q.interpolate_from_vertices_to_edges()                           
530
531
532               
533##############################################
534#Initialise module
535
536#Optimisation with psyco
537from config import use_psyco
538if use_psyco:
539    try:
540        import psyco
541    except:
542        msg = 'WARNING: psyco (speedup) could not import'+\
543              ', you may want to consider installing it'
544        print msg
545    else:
546        psyco.bind(Domain.update_boundary) 
547        #psyco.bind(Domain.update_timestep)     #Not worth it
548        psyco.bind(Domain.update_conserved_quantities)
549        psyco.bind(Domain.distribute_to_vertices_and_edges)
550       
551
552if __name__ == "__main__":
553    pass
Note: See TracBrowser for help on using the repository browser.