source: anuga_core/source/anuga/abstract_2d_finite_volumes/domain.py @ 4619

Last change on this file since 4619 was 4619, checked in by ole, 16 years ago

Comment

File size: 36.7 KB
Line 
1"""Class Domain - 2D triangular domains for finite-volume computations of
2   conservation laws.
3
4
5   Copyright 2004
6   Ole Nielsen, Stephen Roberts, Duncan Gray
7   Geoscience Australia
8"""
9
10from Numeric import allclose, argmax
11from anuga.config import epsilon
12
13from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
14from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
15     import Boundary
16from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
17     import File_boundary
18from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
19     import Dirichlet_boundary
20from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
21     import Time_boundary
22from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
23     import Transmissive_boundary
24
25from anuga.abstract_2d_finite_volumes.pmesh2domain import pmesh_to_domain
26from anuga.abstract_2d_finite_volumes.region\
27     import Set_region as region_set_region
28
29import types
30
31class Domain(Mesh):
32
33
34    def __init__(self,
35                 source=None,
36                 triangles=None,
37                 boundary=None,
38                 conserved_quantities=None,
39                 other_quantities=None,
40                 tagged_elements=None,
41                 geo_reference=None,
42                 use_inscribed_circle=False,
43                 mesh_filename=None,
44                 use_cache=False,
45                 verbose=False,
46                 full_send_dict=None,
47                 ghost_recv_dict=None,
48                 processor=0,
49                 numproc=1,
50                 number_of_full_nodes=None,
51                 number_of_full_triangles=None):
52
53
54        """Instantiate generic computational Domain.
55
56        Input:
57          source:    Either a mesh filename or coordinates of mesh vertices.
58                     If it is a filename values specified for triangles will
59                     be overridden.
60          triangles: Mesh connectivity (see mesh.py for more information)
61          boundary:  See mesh.py for more information
62
63          conserved_quantities: List of quantity names entering the
64                                conservation equations
65          other_quantities:     List of other quantity names
66
67          tagged_elements:
68          ...
69
70
71        """
72
73        # Determine whether source is a mesh filename or coordinates
74        if type(source) == types.StringType:
75            mesh_filename = source
76        else:
77            coordinates = source
78
79
80        # In case a filename has been specified, extract content
81        if mesh_filename is not None:
82            coordinates, triangles, boundary, vertex_quantity_dict, \
83                         tagged_elements, geo_reference = \
84                         pmesh_to_domain(file_name=mesh_filename,
85                                         use_cache=use_cache,
86                                         verbose=verbose)
87
88
89        # Initialise underlying mesh structure
90        Mesh.__init__(self, coordinates, triangles,
91                      boundary=boundary,
92                      tagged_elements=tagged_elements,
93                      geo_reference=geo_reference,
94                      use_inscribed_circle=use_inscribed_circle,
95                      number_of_full_nodes=number_of_full_nodes,
96                      number_of_full_triangles=number_of_full_triangles,
97                      verbose=verbose)
98
99        if verbose: print 'Initialising Domain'
100        from Numeric import zeros, Float, Int, ones
101        from quantity import Quantity, Conserved_quantity
102
103        # List of quantity names entering
104        # the conservation equations
105        if conserved_quantities is None:
106            self.conserved_quantities = []
107        else:
108            self.conserved_quantities = conserved_quantities
109
110        # List of other quantity names
111        if other_quantities is None:
112            self.other_quantities = []
113        else:
114            self.other_quantities = other_quantities
115
116
117        #Build dictionary of Quantity instances keyed by quantity names
118        self.quantities = {}
119
120        #FIXME: remove later - maybe OK, though....
121        for name in self.conserved_quantities:
122            self.quantities[name] = Conserved_quantity(self)
123        for name in self.other_quantities:
124            self.quantities[name] = Quantity(self)
125
126        #Create an empty list for explicit forcing terms
127        self.forcing_terms = []
128
129        #Setup the ghost cell communication
130        if full_send_dict is None:
131            self.full_send_dict = {}
132        else:
133            self.full_send_dict  = full_send_dict
134
135        # List of other quantity names
136        if ghost_recv_dict is None:
137            self.ghost_recv_dict = {}
138        else:
139            self.ghost_recv_dict = ghost_recv_dict
140
141        self.processor = processor
142        self.numproc = numproc
143
144
145        # Setup Communication Buffers
146        if verbose: print 'Domain: Set up communication buffers (parallel)'
147        self.nsys = len(self.conserved_quantities)
148        for key in self.full_send_dict:
149            buffer_shape = self.full_send_dict[key][0].shape[0]
150            self.full_send_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
151
152
153        for key in self.ghost_recv_dict:
154            buffer_shape = self.ghost_recv_dict[key][0].shape[0]
155            self.ghost_recv_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
156
157
158        # Setup cell full flag
159        # =1 for full
160        # =0 for ghost
161        N = len(self) #number_of_elements
162        self.tri_full_flag = ones(N, Int)
163        for i in self.ghost_recv_dict.keys():
164            for id in self.ghost_recv_dict[i][0]:
165                self.tri_full_flag[id] = 0
166
167        # Test the assumption that all full triangles are store before
168        # the ghost triangles.
169        assert allclose(self.tri_full_flag[:self.number_of_full_nodes],1)
170                       
171
172        #Defaults
173        from anuga.config import max_smallsteps, beta_w, beta_h, epsilon, CFL
174        self.beta_w = beta_w
175        self.beta_h = beta_h
176        self.epsilon = epsilon
177
178        #FIXME: Maybe have separate orders for h-limiter and w-limiter?
179        #Or maybe get rid of order altogether and use beta_w and beta_h
180        self.set_default_order(1)
181        #self.default_order = 1
182        #self._order_ = self.default_order
183
184        self.smallsteps = 0
185        self.max_smallsteps = max_smallsteps
186        self.number_of_steps = 0
187        self.number_of_first_order_steps = 0
188        self.CFL = CFL
189
190        self.boundary_map = None  # Will be populated by set_boundary       
191       
192
193        #Model time
194        self.time = 0.0
195        self.finaltime = None
196        self.min_timestep = self.max_timestep = 0.0
197        self.starttime = 0 #Physical starttime if any (0 is 1 Jan 1970 00:00:00)
198
199        ######OBSOLETE
200        #Origin in UTM coordinates
201        #FIXME: This should be set if read by a msh file
202        #self.zone = zone
203        #self.xllcorner = xllcorner
204        #self.yllcorner = yllcorner
205
206
207        #Checkpointing and storage
208        from anuga.config import default_datadir
209        self.datadir = default_datadir
210        self.simulation_name = 'domain'
211        self.checkpoint = False
212
213        #MH310505 To avoid calculating the flux across each edge twice, keep an integer (boolean) array,
214        #to be used during the flux calculation
215        N = len(self) #number_of_triangles
216        self.already_computed_flux = zeros((N, 3), Int)
217
218        # Storage for maximal speeds computed for each triangle by compute_fluxes
219        # This is used for diagnostics only
220        self.max_speed = zeros(N, Float)
221
222        if mesh_filename is not None:
223            # If the mesh file passed any quantity values
224            # , initialise with these values.
225            if verbose: print 'Domain: Initialising quantity values'
226            self.set_quantity_vertices_dict(vertex_quantity_dict)
227
228
229        if verbose: print 'Domain: Done'
230
231
232
233
234    def set_default_order(self, n):
235        """Set default (spatial) order to either 1 or 2
236        """
237
238        msg = 'Default order must be either 1 or 2. I got %s' %n
239        assert n in [1,2], msg
240
241        self.default_order = n
242        self._order_ = self.default_order
243
244
245    #Public interface to Domain
246    def get_conserved_quantities(self, vol_id, vertex=None, edge=None):
247        """Get conserved quantities at volume vol_id
248
249        If vertex is specified use it as index for vertex values
250        If edge is specified use it as index for edge values
251        If neither are specified use centroid values
252        If both are specified an exeception is raised
253
254        Return value: Vector of length == number_of_conserved quantities
255
256        """
257
258        from Numeric import zeros, Float
259
260        if not (vertex is None or edge is None):
261            msg = 'Values for both vertex and edge was specified.'
262            msg += 'Only one (or none) is allowed.'
263            raise msg
264
265        q = zeros( len(self.conserved_quantities), Float)
266
267        for i, name in enumerate(self.conserved_quantities):
268            Q = self.quantities[name]
269            if vertex is not None:
270                q[i] = Q.vertex_values[vol_id, vertex]
271            elif edge is not None:
272                q[i] = Q.edge_values[vol_id, edge]
273            else:
274                q[i] = Q.centroid_values[vol_id]
275
276        return q
277
278    def set_time(self, time=0.0):
279        """Set the model time (seconds)"""
280
281        self.time = time
282
283    def get_time(self):
284        """Get the model time (seconds)"""
285
286        return self.time
287
288    def set_quantity_vertices_dict(self, quantity_dict):
289        """Set values for named quantities.
290        The index is the quantity
291
292        name: Name of quantity
293        X: Compatible list, Numeric array, const or function (see below)
294
295        The values will be stored in elements following their
296        internal ordering.
297
298        """
299        for key in quantity_dict.keys():
300            self.set_quantity(key, quantity_dict[key], location='vertices')
301
302
303    def set_quantity(self, name, *args, **kwargs):
304        """Set values for named quantity
305
306
307        One keyword argument is documented here:
308        expression = None, # Arbitrary expression
309
310        expression:
311          Arbitrary expression involving quantity names
312
313        See Quantity.set_values for further documentation.
314        """
315
316        #FIXME (Ole): Allow new quantities here
317        #from quantity import Quantity, Conserved_quantity
318        #Create appropriate quantity object
319        ##if name in self.conserved_quantities:
320        ##    self.quantities[name] = Conserved_quantity(self)
321        ##else:
322        ##    self.quantities[name] = Quantity(self)
323
324
325        #Do the expression stuff
326        if kwargs.has_key('expression'):
327            expression = kwargs['expression']
328            del kwargs['expression']
329
330            Q = self.create_quantity_from_expression(expression)
331            kwargs['quantity'] = Q
332
333        #Assign values
334        self.quantities[name].set_values(*args, **kwargs)
335
336
337    def get_quantity_names(self):
338        """Get a list of all the quantity names that this domain is aware of.
339        Any value in the result should be a valid input to get_quantity.
340        """
341        return self.quantities.keys()
342
343    def get_quantity(self, name, location='vertices', indices = None):
344        """Get quantity object.
345
346        name: Name of quantity
347
348        See methods inside the quantity object for more options
349
350        FIXME: clean input args
351        """
352
353        return self.quantities[name] #.get_values( location, indices = indices)
354
355
356    def get_quantity_object(self, name):
357        """Get object for named quantity
358
359        name: Name of quantity
360
361        FIXME: Obsolete
362        """
363
364        print 'get_quantity_object has been deprecated. Please use get_quantity'
365        return self.quantities[name]
366
367
368    def create_quantity_from_expression(self, expression):
369        """Create new quantity from other quantities using arbitrary expression
370
371        Combine existing quantities in domain using expression and return
372        result as a new quantity.
373
374        Note, the new quantity could e.g. be used in set_quantity
375
376        Valid expressions are limited to operators defined in class Quantity
377
378        Examples creating derived quantities:
379
380            Depth = domain.create_quantity_from_expression('stage-elevation')
381
382            exp = '(xmomentum*xmomentum + ymomentum*ymomentum)**0.5')       
383            Absolute_momentum = domain.create_quantity_from_expression(exp)
384
385        """
386
387        from anuga.abstract_2d_finite_volumes.util import\
388             apply_expression_to_dictionary
389       
390        return apply_expression_to_dictionary(expression, self.quantities)
391
392
393
394    #def modify_boundary(self, boundary_map):
395    #    """Modify existing boundary by elements in boundary map#
396    #
397    #    Input:#
398    #
399    #    boundary_map: Dictionary mapping tags to boundary objects
400    #
401    #    See set_boundary for more details on how this works
402    #
403    #      OBSOLETE
404    #    """
405    #
406    #    for key in boundary_map.keys():
407    #        self.boundary_map[key] = boundary_map[key]
408    #
409    #    self.set_boundary(self.boundary_map)
410       
411       
412
413    def set_boundary(self, boundary_map):
414        """Associate boundary objects with tagged boundary segments.
415
416        Input boundary_map is a dictionary of boundary objects keyed
417        by symbolic tags to matched against tags in the internal dictionary
418        self.boundary.
419
420        As result one pointer to a boundary object is stored for each vertex
421        in the list self.boundary_objects.
422        More entries may point to the same boundary object
423
424        Schematically the mapping is from two dictionaries to one list
425        where the index is used as pointer to the boundary_values arrays
426        within each quantity.
427
428        self.boundary:          (vol_id, edge_id): tag
429        boundary_map (input):   tag: boundary_object
430        ----------------------------------------------
431        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
432
433
434        Pre-condition:
435          self.boundary has been built.
436
437        Post-condition:
438          self.boundary_objects is built
439
440        If a tag from the domain doesn't appear in the input dictionary an
441        exception is raised.
442        However, if a tag is not used to the domain, no error is thrown.
443        FIXME: This would lead to implementation of a
444        default boundary condition
445
446        Note: If a segment is listed in the boundary dictionary and if it is
447        not None, it *will* become a boundary -
448        even if there is a neighbouring triangle.
449        This would be the case for internal boundaries
450
451        Boundary objects that are None will be skipped.
452
453        If a boundary_map has already been set
454        (i.e. set_boundary has been called before), the old boundary map
455        will be updated with new values. The new map need not define all
456        boundary tags, and can thus change only those that are needed.
457
458        FIXME: If set_boundary is called multiple times and if Boundary
459        object is changed into None, the neighbour structure will not be
460        restored!!!
461
462
463        """
464
465        if self.boundary_map is None:
466            # This the first call to set_boundary. Store
467            # map for later updates and for use with boundary_stats.
468            self.boundary_map = boundary_map
469        else:   
470            # This is a modification of an already existing map
471            # Update map an proceed normally
472
473            for key in boundary_map.keys():
474                self.boundary_map[key] = boundary_map[key]
475               
476       
477        #FIXME: Try to remove the sorting and fix test_mesh.py
478        x = self.boundary.keys()
479        x.sort()
480
481        #Loop through edges that lie on the boundary and associate them with
482        #callable boundary objects depending on their tags
483        self.boundary_objects = []       
484        for k, (vol_id, edge_id) in enumerate(x):
485            tag = self.boundary[ (vol_id, edge_id) ]
486
487            if self.boundary_map.has_key(tag):
488                B = self.boundary_map[tag]  #Get callable boundary object
489
490                if B is not None:
491                    self.boundary_objects.append( ((vol_id, edge_id), B) )
492                    self.neighbours[vol_id, edge_id] = \
493                                            -len(self.boundary_objects)
494                else:
495                    pass
496                    #FIXME: Check and perhaps fix neighbour structure
497
498
499            else:
500                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
501                msg += 'bound to a boundary object.\n'
502                msg += 'All boundary tags defined in domain must appear '
503                msg += 'in the supplied dictionary.\n'
504                msg += 'The tags are: %s' %self.get_boundary_tags()
505                raise msg
506
507
508    def set_region(self, *args, **kwargs):
509        """
510        This method is used to set quantities based on a regional tag.
511       
512        It is most often called with the following parameters;
513        (self, tag, quantity, X, location='vertices')
514        tag: the name of the regional tag used to specify the region
515        quantity: Name of quantity to change
516        X: const or function - how the quantity is changed
517        location: Where values are to be stored.
518            Permissible options are: vertices, centroid and unique vertices
519
520        A callable region class or a list of callable region classes
521        can also be passed into this function.
522        """
523        #print "*args", args
524        #print "**kwargs", kwargs
525        if len(args) == 1:
526            self._set_region(*args, **kwargs)
527        else:
528            #Assume it is arguments for the region.set_region function
529            func = region_set_region(*args, **kwargs)
530            self._set_region(func)
531           
532       
533    def _set_region(self, functions):
534        # The order of functions in the list is used.
535        if type(functions) not in [types.ListType,types.TupleType]:
536            functions = [functions]
537        for function in functions:
538            for tag in self.tagged_elements.keys():
539                function(tag, self.tagged_elements[tag], self)
540
541
542    #MISC
543    def check_integrity(self):
544        Mesh.check_integrity(self)
545
546        for quantity in self.conserved_quantities:
547            msg = 'Conserved quantities must be a subset of all quantities'
548            assert quantity in self.quantities, msg
549
550        ##assert hasattr(self, 'boundary_objects')
551
552    def write_time(self, track_speeds=False):
553        print self.timestepping_statistics(track_speeds)
554
555
556    def timestepping_statistics(self, track_speeds=False):
557        """Return string with time stepping statistics for printing or logging
558
559        Optional boolean keyword track_speeds decides whether to report location of
560        smallest timestep as well as a histogram and percentile report.
561        """
562
563        from anuga.utilities.numerical_tools import histogram, create_bins
564       
565
566        msg = ''
567        if self.min_timestep == self.max_timestep:
568            msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
569                   %(self.time, self.min_timestep, self.number_of_steps,
570                     self.number_of_first_order_steps)
571        elif self.min_timestep > self.max_timestep:
572            msg += 'Time = %.4f, steps=%d (%d)'\
573                   %(self.time, self.number_of_steps,
574                     self.number_of_first_order_steps)
575        else:
576            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
577                   %(self.time, self.min_timestep,
578                     self.max_timestep, self.number_of_steps,
579                     self.number_of_first_order_steps)
580
581        if track_speeds is True:
582            msg += '\n'
583
584
585            #Setup 10 bins for speed histogram
586            bins = create_bins(self.max_speed, 10)
587            hist = histogram(self.max_speed, bins)
588
589            msg += '------------------------------------------------\n'
590            msg += '  Speeds in [%f, %f]\n' %(min(self.max_speed), max(self.max_speed))
591            msg += '  Histogram:\n'
592
593            hi = bins[0]
594            for i, count in enumerate(hist):
595                lo = hi
596                if i+1 < len(bins):
597                    #Open upper interval               
598                    hi = bins[i+1]
599                    msg += '    [%f, %f[: %d\n' %(lo, hi, count)               
600                else:
601                    #Closed upper interval
602                    hi = max(self.max_speed)
603                    msg += '    [%f, %f]: %d\n' %(lo, hi, count)
604
605
606            N = len(self.max_speed)
607            if N > 10:
608                msg += '  Percentiles (10%):\n'
609                speed = self.max_speed.tolist()
610                speed.sort()
611
612                k = 0
613                lower = min(speed)
614                for i, a in enumerate(speed):       
615                    if i % (N/10) == 0 and i != 0: #For every 10% of the sorted speeds
616                        msg += '    %d speeds in [%f, %f]\n' %(i-k, lower, a)
617                        lower = a
618                        k = i
619                       
620                msg += '    %d speeds in [%f, %f]\n'\
621                       %(N-k, lower, max(speed))                   
622               
623                     
624           
625
626               
627           
628            # Find index of largest computed flux speed
629            k = argmax(self.max_speed)
630
631            x, y = self.get_centroid_coordinates()[k]
632            radius = self.get_radii()[k]
633            area = self.get_areas()[k]     
634            max_speed = self.max_speed[k]           
635
636            msg += '  Triangle #%d with centroid (%.4f, %.4f), ' %(k, x, y)
637            msg += 'area = %.4f and radius = %.4f ' %(area, radius)
638            msg += 'had the largest computed speed: %.6f m/s ' %(max_speed)
639            if max_speed > 0.0:
640                msg += '(timestep=%.6f)\n' %(radius/max_speed)
641            else:
642                msg += '(timestep=%.6f)\n' %(0)     
643           
644            # Report all quantity values at vertices
645            msg += '    Quantity \t vertex values\t\t\t\t\t centroid values\n'
646            for name in self.quantities:
647                q = self.quantities[name]
648                X,Y,A,V = q.get_vertex_values()               
649               
650                s = '    %s:\t %.4f,\t %.4f,\t %.4f,\t %.4f\n'\
651                    %(name, A[3*k], A[3*k+1], A[3*k+2], q.get_values(location='centroids')[k]) 
652
653                msg += s
654
655        return msg
656
657
658    def write_boundary_statistics(self, quantities = None, tags = None):
659        print self.boundary_statistics(quantities, tags)
660
661    def boundary_statistics(self, quantities = None, tags = None):
662        """Output statistics about boundary forcing at each timestep
663
664
665        Input:
666          quantities: either None, a string or a list of strings naming the quantities to be reported
667          tags:       either None, a string or a list of strings naming the tags to be reported
668
669
670        Example output:
671        Tag 'wall':
672            stage in [2, 5.5]
673            xmomentum in []
674            ymomentum in []
675        Tag 'ocean'
676
677
678        If quantities are specified only report on those. Otherwise take all conserved quantities.
679        If tags are specified only report on those, otherwise take all tags.
680
681        """
682
683        #Input checks
684        import types, string
685
686        if quantities is None:
687            quantities = self.conserved_quantities
688        elif type(quantities) == types.StringType:
689            quantities = [quantities] #Turn it into a list
690
691        msg = 'Keyword argument quantities must be either None, '
692        msg += 'string or list. I got %s' %str(quantities)
693        assert type(quantities) == types.ListType, msg
694
695
696        if tags is None:
697            tags = self.get_boundary_tags()
698        elif type(tags) == types.StringType:
699            tags = [tags] #Turn it into a list
700
701        msg = 'Keyword argument tags must be either None, '
702        msg += 'string or list. I got %s' %str(tags)
703        assert type(tags) == types.ListType, msg
704
705        #Determine width of longest quantity name (for cosmetic purposes)
706        maxwidth = 0
707        for name in quantities:
708            w = len(name)
709            if w > maxwidth:
710                maxwidth = w
711
712        #Output stats
713        msg = 'Boundary values at time %.4f:\n' %self.time
714        for tag in tags:
715            msg += '    %s:\n' %tag
716
717            for name in quantities:
718                q = self.quantities[name]
719
720                #Find range of boundary values for tag and q
721                maxval = minval = None
722                for i, ((vol_id, edge_id), B) in\
723                        enumerate(self.boundary_objects):
724                    if self.boundary[(vol_id, edge_id)] == tag:
725                        v = q.boundary_values[i]
726                        if minval is None or v < minval: minval = v
727                        if maxval is None or v > maxval: maxval = v
728
729                if minval is None or maxval is None:
730                    msg += '        Sorry no information available about' +\
731                           ' tag %s and quantity %s\n' %(tag, name)
732                else:
733                    msg += '        %s in [%12.8f, %12.8f]\n'\
734                           %(string.ljust(name, maxwidth), minval, maxval)
735
736
737        return msg
738
739
740    def get_name(self):
741        return self.simulation_name
742
743    def set_name(self, name):
744        """Assign a name to this simulation.
745        This will be used to identify the output sww file.
746
747        """
748        if name.endswith('.sww'):
749            name = name[:-4]
750           
751        self.simulation_name = name
752
753    def get_datadir(self):
754        return self.datadir
755
756    def set_datadir(self, name):
757        self.datadir = name
758
759
760
761    #def set_defaults(self):
762    #    """Set default values for uninitialised quantities.
763    #    Should be overridden or specialised by specific modules
764    #    """#
765    #
766    #    for name in self.conserved_quantities + self.other_quantities:
767    #        self.set_quantity(name, 0.0)
768
769
770    ###########################
771    #Main components of evolve
772
773    def evolve(self,
774               yieldstep = None,
775               finaltime = None,
776               duration = None,
777               skip_initial_step = False):
778        """Evolve model through time starting from self.starttime.
779
780
781        yieldstep: Interval between yields where results are stored,
782                   statistics written and domain inspected or
783                   possibly modified. If omitted the internal predefined
784                   max timestep is used.
785                   Internally, smaller timesteps may be taken.
786
787        duration: Duration of simulation
788
789        finaltime: Time where simulation should end. This is currently
790        relative time.  So it's the same as duration.
791
792        If both duration and finaltime are given an exception is thrown.
793
794
795        skip_initial_step: Boolean flag that decides whether the first
796        yield step is skipped or not. This is useful for example to avoid
797        duplicate steps when multiple evolve processes are dove tailed.
798
799
800        Evolve is implemented as a generator and is to be called as such, e.g.
801
802        for t in domain.evolve(yieldstep, finaltime):
803            <Do something with domain and t>
804
805
806        All times are given in seconds
807
808        """
809
810        from anuga.config import min_timestep, max_timestep, epsilon
811
812        #FIXME: Maybe lump into a larger check prior to evolving
813        msg = 'Boundary tags must be bound to boundary objects before '
814        msg += 'evolving system, '
815        msg += 'e.g. using the method set_boundary.\n'
816        msg += 'This system has the boundary tags %s '\
817               %self.get_boundary_tags()
818        assert hasattr(self, 'boundary_objects'), msg
819
820
821        if yieldstep is None:
822            yieldstep = max_timestep
823        else:
824            yieldstep = float(yieldstep)
825
826        self._order_ = self.default_order
827
828
829        if finaltime is not None and duration is not None:
830            #print 'F', finaltime, duration
831            msg = 'Only one of finaltime and duration may be specified'
832            raise msg
833        else:
834            if finaltime is not None:
835                self.finaltime = float(finaltime)
836            if duration is not None:
837                self.finaltime = self.starttime + float(duration)
838
839
840
841
842        self.yieldtime = 0.0 #Time between 'yields'
843
844        #Initialise interval of timestep sizes (for reporting only)
845        self.min_timestep = max_timestep
846        self.max_timestep = min_timestep
847        self.number_of_steps = 0
848        self.number_of_first_order_steps = 0
849
850        #update ghosts
851        self.update_ghosts()
852
853        #Initial update of vertex and edge values
854        self.distribute_to_vertices_and_edges()
855
856        #Initial update boundary values
857        self.update_boundary()
858
859        #Or maybe restore from latest checkpoint
860        if self.checkpoint is True:
861            self.goto_latest_checkpoint()
862
863        if skip_initial_step is False:
864            yield(self.time)  #Yield initial values
865
866        while True:
867            #Compute fluxes across each element edge
868            self.compute_fluxes()
869
870            #Update timestep to fit yieldstep and finaltime
871            self.update_timestep(yieldstep, finaltime)
872
873            #Update conserved quantities
874            self.update_conserved_quantities()
875
876            #update ghosts
877            self.update_ghosts()
878
879            #Update vertex and edge values
880            self.distribute_to_vertices_and_edges()
881
882            #Update boundary values
883            self.update_boundary()
884
885            #Update time
886            self.time += self.timestep
887            self.yieldtime += self.timestep
888            self.number_of_steps += 1
889            if self._order_ == 1:
890                self.number_of_first_order_steps += 1
891
892            #Yield results
893            if finaltime is not None and self.time >= finaltime-epsilon:
894
895                if self.time > finaltime:
896                    #FIXME (Ole, 30 April 2006): Do we need this check?
897                    print 'WARNING (domain.py): time overshot finaltime. Contact Ole.Nielsen@ga.gov.au'
898                    self.time = finaltime
899
900                # Yield final time and stop
901                self.time = finaltime
902                yield(self.time)
903                break
904
905
906            if self.yieldtime >= yieldstep:
907                # Yield (intermediate) time and allow inspection of domain
908
909                if self.checkpoint is True:
910                    self.store_checkpoint()
911                    self.delete_old_checkpoints()
912
913                # Pass control on to outer loop for more specific actions
914                yield(self.time)
915
916                # Reinitialise
917                self.yieldtime = 0.0
918                self.min_timestep = max_timestep
919                self.max_timestep = min_timestep
920                self.number_of_steps = 0
921                self.number_of_first_order_steps = 0
922
923
924    def evolve_to_end(self, finaltime = 1.0):
925        """Iterate evolve all the way to the end
926        """
927
928        for _ in self.evolve(yieldstep=None, finaltime=finaltime):
929            pass
930
931
932
933    def update_boundary(self):
934        """Go through list of boundary objects and update boundary values
935        for all conserved quantities on boundary.
936        It is assumed that the ordering of conserved quantities is
937        consistent between the domain and the boundary object, i.e.
938        the jth element of vector q must correspond to the jth conserved
939        quantity in domain.
940        """
941
942        #FIXME: Update only those that change (if that can be worked out)
943        #FIXME: Boundary objects should not include ghost nodes.
944        for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
945            if B is None:
946                print 'WARNING: Ignored boundary segment %d (None)'
947            else:
948                q = B.evaluate(vol_id, edge_id)
949
950                for j, name in enumerate(self.conserved_quantities):
951                    Q = self.quantities[name]
952                    Q.boundary_values[i] = q[j]
953
954
955    def compute_fluxes(self):
956        msg = 'Method compute_fluxes must be overridden by Domain subclass'
957        raise msg
958
959
960    def update_timestep(self, yieldstep, finaltime):
961
962        from anuga.config import min_timestep, max_timestep
963
964        # self.timestep is calculated from speed of characteristics
965        # Apply CFL condition here
966        timestep = min(self.CFL*self.timestep, max_timestep)
967
968        #Record maximal and minimal values of timestep for reporting
969        self.max_timestep = max(timestep, self.max_timestep)
970        self.min_timestep = min(timestep, self.min_timestep)
971
972        #Protect against degenerate time steps
973        if timestep < min_timestep:
974
975            #Number of consecutive small steps taken b4 taking action
976            self.smallsteps += 1
977
978            if self.smallsteps > self.max_smallsteps:
979                self.smallsteps = 0 #Reset
980
981                if self._order_ == 1:
982                    msg = 'WARNING: Too small timestep %.16f reached '\
983                          %timestep
984                    msg += 'even after %d steps of 1 order scheme'\
985                           %self.max_smallsteps
986                    print msg
987                    timestep = min_timestep  #Try enforcing min_step
988
989
990                    print self.timestepping_statistics(track_speeds=True)
991
992
993                    raise Exception
994                else:
995                    #Try to overcome situation by switching to 1 order
996                    self._order_ = 1
997
998        else:
999            self.smallsteps = 0
1000            if self._order_ == 1 and self.default_order == 2:
1001                self._order_ = 2
1002
1003
1004        #Ensure that final time is not exceeded
1005        if finaltime is not None and self.time + timestep > finaltime :
1006            timestep = finaltime-self.time
1007
1008        #Ensure that model time is aligned with yieldsteps
1009        if self.yieldtime + timestep > yieldstep:
1010            timestep = yieldstep-self.yieldtime
1011
1012        self.timestep = timestep
1013
1014
1015
1016    def compute_forcing_terms(self):
1017        """If there are any forcing functions driving the system
1018        they should be defined in Domain subclass and appended to
1019        the list self.forcing_terms
1020        """
1021
1022        for f in self.forcing_terms:
1023            f(self)
1024
1025
1026
1027    def update_conserved_quantities(self):
1028        """Update vectors of conserved quantities using previously
1029        computed fluxes specified forcing functions.
1030        """
1031
1032        from Numeric import ones, sum, equal, Float
1033
1034        N = len(self) #number_of_triangles
1035        d = len(self.conserved_quantities)
1036
1037        timestep = self.timestep
1038
1039        #Compute forcing terms
1040        self.compute_forcing_terms()
1041
1042        #Update conserved_quantities
1043        for name in self.conserved_quantities:
1044            Q = self.quantities[name]
1045            Q.update(timestep)
1046
1047            #Clean up
1048            #Note that Q.explicit_update is reset by compute_fluxes
1049
1050            #MH090605 commented out the following since semi_implicit_update is now re-initialized
1051            #at the end of the _update function in quantity_ext.c (This is called by the
1052            #preceeding Q.update(timestep) statement above).
1053            #For run_profile.py with N=128, the time of update_conserved_quantities is cut from 14.00 secs
1054            #to 8.35 secs
1055
1056            #Q.semi_implicit_update[:] = 0.0
1057
1058    def update_ghosts(self):
1059        pass
1060
1061    def distribute_to_vertices_and_edges(self):
1062        """Extrapolate conserved quantities from centroid to
1063        vertices and edge-midpoints for each volume
1064
1065        Default implementation is straight first order,
1066        i.e. constant values throughout each element and
1067        no reference to non-conserved quantities.
1068        """
1069
1070        for name in self.conserved_quantities:
1071            Q = self.quantities[name]
1072            if self._order_ == 1:
1073                Q.extrapolate_first_order()
1074            elif self._order_ == 2:
1075                Q.extrapolate_second_order()
1076                Q.limit()
1077            else:
1078                raise 'Unknown order'
1079            Q.interpolate_from_vertices_to_edges()
1080
1081
1082    def centroid_norm(self, quantity, normfunc):
1083        """Calculate the norm of the centroid values
1084        of a specific quantity, using normfunc.
1085
1086        normfunc should take a list to a float.
1087
1088        common normfuncs are provided in the module utilities.norms
1089        """
1090        return normfunc(self.quantities[quantity].centroid_values)
1091
1092
1093
1094##############################################
1095#Initialise module
1096
1097#Optimisation with psyco
1098from anuga.config import use_psyco
1099if use_psyco:
1100    try:
1101        import psyco
1102    except:
1103        import os
1104        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1105            pass
1106            #Psyco isn't supported on 64 bit systems, but it doesn't matter
1107        else:
1108            msg = 'WARNING: psyco (speedup) could not import'+\
1109                  ', you may want to consider installing it'
1110            print msg
1111    else:
1112        psyco.bind(Domain.update_boundary)
1113        #psyco.bind(Domain.update_timestep)     #Not worth it
1114        psyco.bind(Domain.update_conserved_quantities)
1115        psyco.bind(Domain.distribute_to_vertices_and_edges)
1116
1117
1118if __name__ == "__main__":
1119    pass
Note: See TracBrowser for help on using the repository browser.