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

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

Work on file boundary (cyclone)

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