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

Last change on this file since 488 was 476, checked in by ole, 21 years ago

Reimplemented unit tests for semi implicit updates and fixed comments.

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