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

Last change on this file since 1506 was 1506, checked in by matthew, 19 years ago

The re-initialization of semi_implicit_update[k] to 0.0 is now done in the _update C function of quantity_ext.c, instead of the method update_conserved_quantities of domain.py. For run_profile.py, with N=128, the time spend in update_conserved_quantities is cut from 14.00 secs to 8.31 secs (averaged over three runs).

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