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

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