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

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

set_function_values now handles a subset of triangles. Old and new code.

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