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

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

First stab at wind stress from file

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