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

Last change on this file since 393 was 389, checked in by duncan, 20 years ago

add vert atts to pmesh2domain/pyvolution

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