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

Last change on this file since 469 was 469, checked in by duncan, 20 years ago

removing debug info

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