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

Last change on this file since 4679 was 4679, checked in by ole, 17 years ago

Typos and more meaningful error message

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