source: storm_surge/pyvolution/domain.py @ 1

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

initial import

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