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

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

Cleaned up and simplified data manager + getters and setters in domain

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