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

Last change on this file since 544 was 505, checked in by ole, 20 years ago

this is a test from GA

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    def set_name(self, name):
235        self.filename = name   
236
237    #def set_defaults(self):
238    #    """Set default values for uninitialised quantities.
239    #    Should be overridden or specialised by specific modules
240    #    """#
241    #
242    #    for name in self.conserved_quantities + self.other_quantities:
243    #        self.set_quantity(name, 0.0)       
244               
245
246    ###########################
247    #Main components of evolve
248
249    def evolve(self, yieldstep = None, finaltime = None):
250        """Evolve model from time=0.0 to finaltime yielding results
251        every yieldstep.
252       
253        Internally, smaller timesteps may be taken.
254
255        Evolve is implemented as a generator and is to be called as such, e.g.
256       
257        for t in domain.evolve(timestep, yieldstep, finaltime):
258            <Do something with domain and t>
259       
260        """
261
262        #import data_manager
263        from config import min_timestep, max_timestep, epsilon
264
265
266        ##self.set_defaults()
267
268        if yieldstep is None:
269            yieldstep = max_timestep
270
271        self.order = self.default_order
272       
273           
274        self.yieldtime = 0.0 #Time between 'yields'
275
276        #Initialise interval of timestep sizes (for reporting only)
277        self.min_timestep = max_timestep
278        self.max_timestep = min_timestep
279        self.finaltime = finaltime
280        self.number_of_steps = 0
281        self.number_of_first_order_steps = 0               
282
283        #Initial update of vertex and edge values
284        self.distribute_to_vertices_and_edges()
285       
286       
287        #Or maybe restore from latest checkpoint
288        if self.checkpoint is True:
289            self.goto_latest_checkpoint()
290
291           
292        yield(self.time)  #Yield initial values
293       
294        while True:
295            #Update boundary values
296            self.update_boundary()           
297
298            #Compute fluxes across each element edge
299            self.compute_fluxes()
300
301            #Update timestep to fit yieldstep and finaltime
302            self.update_timestep(yieldstep, finaltime)
303
304            #Update conserved quantities
305            self.update_conserved_quantities()           
306
307            #Update vertex and edge values
308            self.distribute_to_vertices_and_edges()
309           
310            #Update time   
311            self.time += self.timestep
312            self.yieldtime += self.timestep
313            self.number_of_steps += 1
314            if self.order == 1:
315                self.number_of_first_order_steps += 1       
316
317            #Yield results
318            if finaltime is not None and abs(self.time - finaltime) < epsilon:
319                # Yield final time and stop
320                yield(self.time)
321                break
322
323               
324            if abs(self.yieldtime - yieldstep) < epsilon: 
325                # Yield (intermediate) time and allow inspection of domain
326
327                if self.checkpoint is True:
328                    self.store_checkpoint()
329                    self.delete_old_checkpoints()
330                   
331                #Pass control on to outer loop for more specific actions   
332                yield(self.time) 
333
334                # Reinitialise
335                self.yieldtime = 0.0               
336                self.min_timestep = max_timestep
337                self.max_timestep = min_timestep
338                self.number_of_steps = 0
339                self.number_of_first_order_steps = 0                       
340
341           
342    def evolve_to_end(self, finaltime = 1.0):
343        """Iterate evolve all the way to the end
344        """
345
346        for _ in self.evolve(yieldstep=None, finaltime=finaltime):           
347            pass
348       
349
350   
351    def update_boundary(self):
352        """Go through list of boundary objects and update boundary values
353        for all conserved quantities on boundary.
354        """
355
356        #FIXME: Update only those that change (if that can be worked out)
357        for i, B in enumerate(self.boundary_objects):
358            vol_id, edge_id = self.boundary_segments[i]
359            q = B.evaluate(vol_id, edge_id)
360
361            for j, name in enumerate(self.conserved_quantities):
362                Q = self.quantities[name]
363                Q.boundary_values[i] = q[j]
364
365
366    def compute_fluxes(self):
367        msg = 'Method compute_fluxes must be overridden by Domain subclass'
368        raise msg
369
370
371    def update_timestep(self, yieldstep, finaltime):
372
373        from config import min_timestep
374   
375        timestep = self.timestep
376   
377        #Record maximal and minimal values of timestep for reporting   
378        self.max_timestep = max(timestep, self.max_timestep)
379        self.min_timestep = min(timestep, self.min_timestep)
380   
381        #Protect against degenerate time steps
382        if timestep < min_timestep:
383
384            #Number of consecutive small steps taken b4 taking action
385            self.smallsteps += 1 
386
387            if self.smallsteps > self.max_smallsteps:
388                self.smallsteps = 0 #Reset
389               
390                if self.order == 1:
391                    msg = 'Too small timestep %.16f reached ' %timestep
392                    msg += 'even after %d steps of 1 order scheme'\
393                           %self.max_smallsteps
394
395                    raise msg
396                else:
397                    #Try to overcome situation by switching to 1 order
398                    self.order = 1
399
400        else:
401            self.smallsteps = 0
402            if self.order == 1 and self.default_order == 2:
403                self.order = 2
404                   
405
406        #Ensure that final time is not exceeded
407        if finaltime is not None and self.time + timestep > finaltime:
408            timestep = finaltime-self.time
409
410        #Ensure that model time is aligned with yieldsteps
411        if self.yieldtime + timestep > yieldstep:
412            timestep = yieldstep-self.yieldtime
413
414        self.timestep = timestep
415
416
417
418    def compute_forcing_terms(self):         
419        """If there are any forcing functions driving the system
420        they should be defined in Domain subclass and appended to
421        the list self.forcing_terms
422        """
423
424        for f in self.forcing_terms:
425            f(self)
426
427       
428
429    def update_conserved_quantities(self):
430        """Update vectors of conserved quantities using previously
431        computed fluxes specified forcing functions.
432        """
433
434        from Numeric import ones, sum, equal, Float
435           
436        N = self.number_of_elements
437        d = len(self.conserved_quantities)
438       
439        timestep = self.timestep
440
441        #Compute forcing terms   
442        self.compute_forcing_terms()   
443
444        #Update conserved_quantities
445        for name in self.conserved_quantities:
446            Q = self.quantities[name]
447            Q.update(timestep)
448
449            #Clean up
450            #Note that Q.explicit_update is reset by compute_fluxes           
451            Q.semi_implicit_update[:] = 0.0
452
453           
454
455    def distribute_to_vertices_and_edges(self):
456        """Extrapolate conserved quantities from centroid to
457        vertices and edge-midpoints for each volume
458
459        Default implementation is straight first order,
460        i.e. constant values throughout each element and
461        no reference to non-conserved quantities.
462        """
463
464        for name in self.conserved_quantities:
465            Q = self.quantities[name]
466            if self.order == 1:
467                Q.extrapolate_first_order()
468            elif self.order == 2:
469                Q.extrapolate_second_order()
470                Q.limit()                               
471            else:
472                raise 'Unknown order'
473            Q.interpolate_from_vertices_to_edges()                           
474
475
476               
477##############################################
478#Initialise module
479
480#Optimisation with psyco
481from config import use_psyco
482if use_psyco:
483    try:
484        import psyco
485    except:
486        msg = 'WARNING: psyco (speedup) could not import'+\
487              ', you may want to consider installing it'
488        print msg
489    else:
490        psyco.bind(Domain.update_boundary) 
491        #psyco.bind(Domain.update_timestep)     #Not worth it
492        psyco.bind(Domain.update_conserved_quantities)
493        psyco.bind(Domain.distribute_to_vertices_and_edges)
494       
495
496if __name__ == "__main__":
497    pass
Note: See TracBrowser for help on using the repository browser.