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

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

Changed timestepping_statistics to output real time seconds as well
Also comments and formatting

File size: 54.5 KB
RevLine 
[3804]1"""Class Domain - 2D triangular domains for finite-volume computations of
2   conservation laws.
3
4
5   Copyright 2004
[4200]6   Ole Nielsen, Stephen Roberts, Duncan Gray
[3804]7   Geoscience Australia
8"""
9
[4829]10from Numeric import allclose, argmax, zeros, Float
[3817]11from anuga.config import epsilon
[5162]12from anuga.config import beta_euler, beta_rk2
[3817]13
[3804]14from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
15from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
16     import Boundary
17from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
18     import File_boundary
19from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
20     import Dirichlet_boundary
21from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
22     import Time_boundary
23from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
24     import Transmissive_boundary
25
26from anuga.abstract_2d_finite_volumes.pmesh2domain import pmesh_to_domain
27from anuga.abstract_2d_finite_volumes.region\
28     import Set_region as region_set_region
29
[4704]30from anuga.utilities.polygon import inside_polygon
31from anuga.abstract_2d_finite_volumes.util import get_textual_float
32
[3804]33import types
[5421]34from time import time as walltime
[3804]35
[4712]36
37
[3804]38class Domain(Mesh):
39
40
41    def __init__(self,
42                 source=None,
43                 triangles=None,
44                 boundary=None,
45                 conserved_quantities=None,
46                 other_quantities=None,
47                 tagged_elements=None,
48                 geo_reference=None,
49                 use_inscribed_circle=False,
50                 mesh_filename=None,
51                 use_cache=False,
52                 verbose=False,
53                 full_send_dict=None,
54                 ghost_recv_dict=None,
55                 processor=0,
[3926]56                 numproc=1,
[3928]57                 number_of_full_nodes=None,
58                 number_of_full_triangles=None):
[3804]59
60
61        """Instantiate generic computational Domain.
62
63        Input:
64          source:    Either a mesh filename or coordinates of mesh vertices.
65                     If it is a filename values specified for triangles will
66                     be overridden.
67          triangles: Mesh connectivity (see mesh.py for more information)
68          boundary:  See mesh.py for more information
69
70          conserved_quantities: List of quantity names entering the
71                                conservation equations
72          other_quantities:     List of other quantity names
73
74          tagged_elements:
75          ...
76
77
78        """
79
80        # Determine whether source is a mesh filename or coordinates
81        if type(source) == types.StringType:
82            mesh_filename = source
83        else:
84            coordinates = source
85
86
87        # In case a filename has been specified, extract content
88        if mesh_filename is not None:
89            coordinates, triangles, boundary, vertex_quantity_dict, \
90                         tagged_elements, geo_reference = \
91                         pmesh_to_domain(file_name=mesh_filename,
92                                         use_cache=use_cache,
93                                         verbose=verbose)
94
95
96        # Initialise underlying mesh structure
[3928]97        Mesh.__init__(self, coordinates, triangles,
98                      boundary=boundary,
99                      tagged_elements=tagged_elements,
100                      geo_reference=geo_reference,
101                      use_inscribed_circle=use_inscribed_circle,
102                      number_of_full_nodes=number_of_full_nodes,
103                      number_of_full_triangles=number_of_full_triangles,
[3804]104                      verbose=verbose)
105
106        if verbose: print 'Initialising Domain'
107        from Numeric import zeros, Float, Int, ones
[4957]108        from quantity import Quantity
[3804]109
110        # List of quantity names entering
111        # the conservation equations
112        if conserved_quantities is None:
113            self.conserved_quantities = []
114        else:
115            self.conserved_quantities = conserved_quantities
116
117        # List of other quantity names
118        if other_quantities is None:
119            self.other_quantities = []
120        else:
121            self.other_quantities = other_quantities
122
123
124        #Build dictionary of Quantity instances keyed by quantity names
125        self.quantities = {}
126
127        #FIXME: remove later - maybe OK, though....
128        for name in self.conserved_quantities:
[4957]129            self.quantities[name] = Quantity(self)
[3804]130        for name in self.other_quantities:
131            self.quantities[name] = Quantity(self)
132
133        #Create an empty list for explicit forcing terms
134        self.forcing_terms = []
135
136        #Setup the ghost cell communication
137        if full_send_dict is None:
138            self.full_send_dict = {}
139        else:
140            self.full_send_dict  = full_send_dict
141
142        # List of other quantity names
[3926]143        if ghost_recv_dict is None:
144            self.ghost_recv_dict = {}
[3804]145        else:
[3926]146            self.ghost_recv_dict = ghost_recv_dict
[3804]147
148        self.processor = processor
[3926]149        self.numproc = numproc
[3804]150
[3926]151
[3804]152        # Setup Communication Buffers
153        if verbose: print 'Domain: Set up communication buffers (parallel)'
154        self.nsys = len(self.conserved_quantities)
155        for key in self.full_send_dict:
156            buffer_shape = self.full_send_dict[key][0].shape[0]
157            self.full_send_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
158
159
160        for key in self.ghost_recv_dict:
161            buffer_shape = self.ghost_recv_dict[key][0].shape[0]
162            self.ghost_recv_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
163
164
165        # Setup cell full flag
166        # =1 for full
167        # =0 for ghost
[3928]168        N = len(self) #number_of_elements
[5242]169        self.number_of_elements = N
[3804]170        self.tri_full_flag = ones(N, Int)
171        for i in self.ghost_recv_dict.keys():
172            for id in self.ghost_recv_dict[i][0]:
173                self.tri_full_flag[id] = 0
174
[3946]175        # Test the assumption that all full triangles are store before
176        # the ghost triangles.
[5242]177        if not allclose(self.tri_full_flag[:self.number_of_full_nodes],1):
178            print 'WARNING:  Not all full triangles are store before ghost triangles'
[3946]179                       
[3804]180
[4701]181        # Defaults
[4677]182        from anuga.config import max_smallsteps, beta_w, beta_h, epsilon
183        from anuga.config import CFL
[4712]184        from anuga.config import timestepping_method
[4677]185        from anuga.config import protect_against_isolated_degenerate_timesteps
[3804]186        self.beta_w = beta_w
187        self.beta_h = beta_h
188        self.epsilon = epsilon
[4677]189        self.protect_against_isolated_degenerate_timesteps = protect_against_isolated_degenerate_timesteps
190       
[4712]191       
[3804]192
[4701]193        # FIXME: Maybe have separate orders for h-limiter and w-limiter?
194        # Or maybe get rid of order altogether and use beta_w and beta_h
[5421]195        # FIXME (Ole): In any case, this should appear in the config file - not here
[3804]196        self.set_default_order(1)
197
198        self.smallsteps = 0
199        self.max_smallsteps = max_smallsteps
200        self.number_of_steps = 0
201        self.number_of_first_order_steps = 0
202        self.CFL = CFL
[4712]203        self.set_timestepping_method(timestepping_method)
204       
[3804]205        self.boundary_map = None  # Will be populated by set_boundary       
206       
207
[4701]208        # Model time
[3804]209        self.time = 0.0
210        self.finaltime = None
211        self.min_timestep = self.max_timestep = 0.0
[4771]212        self.starttime = 0 # Physical starttime if any
213                           # (0 is 1 Jan 1970 00:00:00)
[4713]214        self.timestep = 0.0
215        self.flux_timestep = 0.0
[3804]216
[5421]217        self.last_walltime = walltime()
218       
[4701]219        # Monitoring
220        self.quantities_to_be_monitored = None
221        self.monitor_polygon = None
222        self.monitor_time_interval = None               
[4704]223        self.monitor_indices = None
[3804]224
[4701]225        # Checkpointing and storage
[3804]226        from anuga.config import default_datadir
227        self.datadir = default_datadir
[3846]228        self.simulation_name = 'domain'
[3804]229        self.checkpoint = False
230
[4771]231        # To avoid calculating the flux across each edge twice,
232        # keep an integer (boolean) array, to be used during the flux
233        # calculation
234        N = len(self) # Number_of_triangles
[3804]235        self.already_computed_flux = zeros((N, 3), Int)
236
[4771]237        # Storage for maximal speeds computed for each triangle by
238        # compute_fluxes
[4829]239        # This is used for diagnostics only (reset at every yieldstep)
[4200]240        self.max_speed = zeros(N, Float)
241
[3804]242        if mesh_filename is not None:
243            # If the mesh file passed any quantity values
244            # , initialise with these values.
245            if verbose: print 'Domain: Initialising quantity values'
246            self.set_quantity_vertices_dict(vertex_quantity_dict)
247
248
249        if verbose: print 'Domain: Done'
250
251
252
253
254
255
[4771]256    #---------------------------   
257    # Public interface to Domain
258    #---------------------------       
[3804]259    def get_conserved_quantities(self, vol_id, vertex=None, edge=None):
260        """Get conserved quantities at volume vol_id
261
262        If vertex is specified use it as index for vertex values
263        If edge is specified use it as index for edge values
264        If neither are specified use centroid values
265        If both are specified an exeception is raised
266
267        Return value: Vector of length == number_of_conserved quantities
268
269        """
270
271        from Numeric import zeros, Float
272
273        if not (vertex is None or edge is None):
274            msg = 'Values for both vertex and edge was specified.'
275            msg += 'Only one (or none) is allowed.'
276            raise msg
277
278        q = zeros( len(self.conserved_quantities), Float)
279
280        for i, name in enumerate(self.conserved_quantities):
281            Q = self.quantities[name]
282            if vertex is not None:
283                q[i] = Q.vertex_values[vol_id, vertex]
284            elif edge is not None:
285                q[i] = Q.edge_values[vol_id, edge]
286            else:
287                q[i] = Q.centroid_values[vol_id]
288
289        return q
290
291    def set_time(self, time=0.0):
292        """Set the model time (seconds)"""
293
294        self.time = time
295
296    def get_time(self):
297        """Get the model time (seconds)"""
298
299        return self.time
300
[4713]301    def set_default_order(self, n):
302        """Set default (spatial) order to either 1 or 2
303        """
304
305        msg = 'Default order must be either 1 or 2. I got %s' %n
306        assert n in [1,2], msg
307
308        self.default_order = n
309        self._order_ = self.default_order
[5162]310
311        if self.default_order == 1:
[5306]312            pass
313            #self.set_timestepping_method('euler')
[5162]314            #self.set_all_limiters(beta_euler)
315
316        if self.default_order == 2:
[5306]317            pass
318            #self.set_timestepping_method('rk2')
[5162]319            #self.set_all_limiters(beta_rk2)
[4713]320       
321
[3804]322    def set_quantity_vertices_dict(self, quantity_dict):
323        """Set values for named quantities.
324        The index is the quantity
325
326        name: Name of quantity
327        X: Compatible list, Numeric array, const or function (see below)
328
329        The values will be stored in elements following their
330        internal ordering.
331
332        """
[4701]333       
334        # FIXME: Could we name this a bit more intuitively
335        # E.g. set_quantities_from_dictionary
[3804]336        for key in quantity_dict.keys():
337            self.set_quantity(key, quantity_dict[key], location='vertices')
338
339
340    def set_quantity(self, name, *args, **kwargs):
341        """Set values for named quantity
342
343
344        One keyword argument is documented here:
345        expression = None, # Arbitrary expression
346
347        expression:
348          Arbitrary expression involving quantity names
349
350        See Quantity.set_values for further documentation.
351        """
352
[4771]353        # Do the expression stuff
[3804]354        if kwargs.has_key('expression'):
355            expression = kwargs['expression']
356            del kwargs['expression']
357
358            Q = self.create_quantity_from_expression(expression)
359            kwargs['quantity'] = Q
360
[4771]361        # Assign values
[3804]362        self.quantities[name].set_values(*args, **kwargs)
363
364
[3963]365    def get_quantity_names(self):
366        """Get a list of all the quantity names that this domain is aware of.
367        Any value in the result should be a valid input to get_quantity.
368        """
369        return self.quantities.keys()
370
[3804]371    def get_quantity(self, name, location='vertices', indices = None):
372        """Get quantity object.
373
374        name: Name of quantity
375
376        See methods inside the quantity object for more options
377
378        FIXME: clean input args
379        """
380
381        return self.quantities[name] #.get_values( location, indices = indices)
382
383
384
385    def create_quantity_from_expression(self, expression):
386        """Create new quantity from other quantities using arbitrary expression
387
388        Combine existing quantities in domain using expression and return
389        result as a new quantity.
390
391        Note, the new quantity could e.g. be used in set_quantity
392
393        Valid expressions are limited to operators defined in class Quantity
394
[4020]395        Examples creating derived quantities:
[3804]396
[4020]397            Depth = domain.create_quantity_from_expression('stage-elevation')
[3804]398
[4897]399            exp = '(xmomentum*xmomentum + ymomentum*ymomentum)**0.5'       
[4020]400            Absolute_momentum = domain.create_quantity_from_expression(exp)
401
[3804]402        """
403
[3829]404        from anuga.abstract_2d_finite_volumes.util import\
405             apply_expression_to_dictionary
406       
[3804]407        return apply_expression_to_dictionary(expression, self.quantities)
408
409
410
411    def set_boundary(self, boundary_map):
412        """Associate boundary objects with tagged boundary segments.
413
414        Input boundary_map is a dictionary of boundary objects keyed
415        by symbolic tags to matched against tags in the internal dictionary
416        self.boundary.
417
418        As result one pointer to a boundary object is stored for each vertex
419        in the list self.boundary_objects.
420        More entries may point to the same boundary object
421
422        Schematically the mapping is from two dictionaries to one list
423        where the index is used as pointer to the boundary_values arrays
424        within each quantity.
425
426        self.boundary:          (vol_id, edge_id): tag
427        boundary_map (input):   tag: boundary_object
428        ----------------------------------------------
429        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
430
431
432        Pre-condition:
433          self.boundary has been built.
434
435        Post-condition:
436          self.boundary_objects is built
437
438        If a tag from the domain doesn't appear in the input dictionary an
439        exception is raised.
440        However, if a tag is not used to the domain, no error is thrown.
441        FIXME: This would lead to implementation of a
442        default boundary condition
443
444        Note: If a segment is listed in the boundary dictionary and if it is
445        not None, it *will* become a boundary -
446        even if there is a neighbouring triangle.
447        This would be the case for internal boundaries
448
449        Boundary objects that are None will be skipped.
450
[3829]451        If a boundary_map has already been set
452        (i.e. set_boundary has been called before), the old boundary map
453        will be updated with new values. The new map need not define all
454        boundary tags, and can thus change only those that are needed.
455
[3804]456        FIXME: If set_boundary is called multiple times and if Boundary
457        object is changed into None, the neighbour structure will not be
458        restored!!!
459
460
461        """
462
[3829]463        if self.boundary_map is None:
464            # This the first call to set_boundary. Store
465            # map for later updates and for use with boundary_stats.
466            self.boundary_map = boundary_map
467        else:   
468            # This is a modification of an already existing map
469            # Update map an proceed normally
[3804]470
[3829]471            for key in boundary_map.keys():
472                self.boundary_map[key] = boundary_map[key]
473               
474       
[4771]475        # FIXME (Ole): Try to remove the sorting and fix test_mesh.py
[3804]476        x = self.boundary.keys()
477        x.sort()
478
[4771]479        # Loop through edges that lie on the boundary and associate them with
480        # callable boundary objects depending on their tags
[3829]481        self.boundary_objects = []       
[3804]482        for k, (vol_id, edge_id) in enumerate(x):
483            tag = self.boundary[ (vol_id, edge_id) ]
484
[3829]485            if self.boundary_map.has_key(tag):
[4771]486                B = self.boundary_map[tag]  # Get callable boundary object
[3804]487
488                if B is not None:
489                    self.boundary_objects.append( ((vol_id, edge_id), B) )
[3829]490                    self.neighbours[vol_id, edge_id] = \
491                                            -len(self.boundary_objects)
[3804]492                else:
493                    pass
494                    #FIXME: Check and perhaps fix neighbour structure
495
496
497            else:
498                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
499                msg += 'bound to a boundary object.\n'
500                msg += 'All boundary tags defined in domain must appear '
[4679]501                msg += 'in set_boundary.\n'
[3804]502                msg += 'The tags are: %s' %self.get_boundary_tags()
503                raise msg
504
505
506    def set_region(self, *args, **kwargs):
507        """
508        This method is used to set quantities based on a regional tag.
509       
510        It is most often called with the following parameters;
511        (self, tag, quantity, X, location='vertices')
512        tag: the name of the regional tag used to specify the region
513        quantity: Name of quantity to change
514        X: const or function - how the quantity is changed
515        location: Where values are to be stored.
516            Permissible options are: vertices, centroid and unique vertices
517
518        A callable region class or a list of callable region classes
519        can also be passed into this function.
520        """
[4771]521
[3804]522        if len(args) == 1:
523            self._set_region(*args, **kwargs)
524        else:
[4771]525            # Assume it is arguments for the region.set_region function
[3804]526            func = region_set_region(*args, **kwargs)
527            self._set_region(func)
528           
529       
530    def _set_region(self, functions):
531        # The order of functions in the list is used.
532        if type(functions) not in [types.ListType,types.TupleType]:
533            functions = [functions]
534        for function in functions:
535            for tag in self.tagged_elements.keys():
536                function(tag, self.tagged_elements[tag], self)
537
538
[4701]539
540
541    def set_quantities_to_be_monitored(self, q,
542                                       polygon=None,
543                                       time_interval=None):
544        """Specify which quantities will be monitored for extrema.
545
546        q must be either:
[4735]547          - the name of a quantity or a derived quantity such as 'stage-elevation'
[4701]548          - a list of quantity names
549          - None
550
551        In the two first cases, the named quantities will be monitored at
552        each internal timestep
553       
554        If q is None, monitoring will be switched off altogether.
555
556        polygon (if specified) will restrict monitoring to triangles inside polygon.
557        If omitted all triangles will be included.
558
559        time_interval (if specified) will restrict monitoring to time steps in
560        that interval. If omitted all timesteps will be included.
561        """
562
[4702]563        from anuga.abstract_2d_finite_volumes.util import\
564             apply_expression_to_dictionary       
565
[4701]566        if q is None:
567            self.quantities_to_be_monitored = None
568            self.monitor_polygon = None
[4704]569            self.monitor_time_interval = None
570            self.monitor_indices = None           
[4701]571            return
572
573        if isinstance(q, basestring):
574            q = [q] # Turn argument into a list
575
[4702]576        # Check correcness and initialise
577        self.quantities_to_be_monitored = {}
[4701]578        for quantity_name in q:
579            msg = 'Quantity %s is not a valid conserved quantity'\
580                  %quantity_name
581           
582
[4702]583            if not quantity_name in self.quantities:
584                # See if this expression is valid
585                apply_expression_to_dictionary(quantity_name, self.quantities)
586
[4704]587            # Initialise extrema information
588            info_block = {'min': None,          # Min value
589                          'max': None,          # Max value
590                          'min_location': None, # Argmin (x, y)
591                          'max_location': None, # Argmax (x, y)
592                          'min_time': None,     # Argmin (t)
593                          'max_time': None}     # Argmax (t)
[4702]594           
[4704]595            self.quantities_to_be_monitored[quantity_name] = info_block
[4702]596
[4704]597           
598
[4701]599        if polygon is not None:
[4704]600            # Check input
601            if isinstance(polygon, basestring):
[4701]602
[4704]603                # Check if multiple quantities were accidentally
604                # given as separate argument rather than a list.
605                msg = 'Multiple quantities must be specified in a list. '
606                msg += 'Not as multiple arguments. '
607                msg += 'I got "%s" as a second argument' %polygon
608               
609                if polygon in self.quantities:
610                    raise Exception, msg
611               
612                try:
613                    apply_expression_to_dictionary(polygon, self.quantities)
614                except:
615                    # At least polygon wasn't an expression involving quantitites
616                    pass
617                else:
618                    raise Exception, msg
619
620                # In any case, we don't allow polygon to be a string
621                msg = 'argument "polygon" must not be a string: '
622                msg += 'I got polygon=\'%s\' ' %polygon
623                raise Exception, msg
624
625
626            # Get indices for centroids that are inside polygon
627            points = self.get_centroid_coordinates(absolute=True)
628            self.monitor_indices = inside_polygon(points, polygon)
629           
630
[4701]631        if time_interval is not None:
[4704]632            assert len(time_interval) == 2
[4701]633
[4702]634       
[4701]635        self.monitor_polygon = polygon
636        self.monitor_time_interval = time_interval       
637       
638
[4771]639    #--------------------------   
640    # Miscellaneous diagnostics
641    #--------------------------       
[3804]642    def check_integrity(self):
643        Mesh.check_integrity(self)
644
645        for quantity in self.conserved_quantities:
646            msg = 'Conserved quantities must be a subset of all quantities'
647            assert quantity in self.quantities, msg
648
649        ##assert hasattr(self, 'boundary_objects')
[4771]650           
[3804]651
[4204]652    def write_time(self, track_speeds=False):
653        print self.timestepping_statistics(track_speeds)
[3804]654
655
[4836]656    def timestepping_statistics(self,
657                                track_speeds=False,
658                                triangle_id=None):
[3804]659        """Return string with time stepping statistics for printing or logging
[4200]660
[4771]661        Optional boolean keyword track_speeds decides whether to report
662        location of smallest timestep as well as a histogram and percentile
[4836]663        report.
664
665        Optional keyword triangle_id can be used to specify a particular
666        triangle rather than the one with the largest speed.
[3804]667        """
668
[4204]669        from anuga.utilities.numerical_tools import histogram, create_bins
670       
671
[4827]672        # qwidth determines the text field used for quantities
673        qwidth = self.qwidth = 12
674
675
[3804]676        msg = ''
[5421]677        #if self.min_timestep == self.max_timestep:
678        #    msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
679        #           %(self.time, self.min_timestep, self.number_of_steps,
680        #             self.number_of_first_order_steps)
681        #elif self.min_timestep > self.max_timestep:
682        #    msg += 'Time = %.4f, steps=%d (%d)'\
683        #           %(self.time, self.number_of_steps,
684        #             self.number_of_first_order_steps)
685        #else:
686        #    msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
687        #           %(self.time, self.min_timestep,
688        #             self.max_timestep, self.number_of_steps,
689        #             self.number_of_first_order_steps)
690                     
691                     
[3804]692        if self.min_timestep == self.max_timestep:
[5421]693            msg += 'Time = %.4f, delta t = %.8f, steps=%d'\
694                   %(self.time, self.min_timestep, self.number_of_steps)
[3804]695        elif self.min_timestep > self.max_timestep:
[5421]696            msg += 'Time = %.4f, steps=%d'\
697                   %(self.time, self.number_of_steps)
[3804]698        else:
[5421]699            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d'\
[3804]700                   %(self.time, self.min_timestep,
[5421]701                     self.max_timestep, self.number_of_steps)
702                                         
703        msg += ' (%ds)' %(walltime() - self.last_walltime)   
704        self.last_walltime = walltime()           
705       
[4204]706        if track_speeds is True:
[4200]707            msg += '\n'
[4204]708
709
[4771]710            # Setup 10 bins for speed histogram
[4204]711            bins = create_bins(self.max_speed, 10)
712            hist = histogram(self.max_speed, bins)
713
714            msg += '------------------------------------------------\n'
[4771]715            msg += '  Speeds in [%f, %f]\n' %(min(self.max_speed),
716                                              max(self.max_speed))
[4204]717            msg += '  Histogram:\n'
718
719            hi = bins[0]
720            for i, count in enumerate(hist):
721                lo = hi
722                if i+1 < len(bins):
[4771]723                    # Open upper interval               
[4204]724                    hi = bins[i+1]
725                    msg += '    [%f, %f[: %d\n' %(lo, hi, count)               
726                else:
[4771]727                    # Closed upper interval
[4204]728                    hi = max(self.max_speed)
729                    msg += '    [%f, %f]: %d\n' %(lo, hi, count)
730
731
732            N = len(self.max_speed)
733            if N > 10:
734                msg += '  Percentiles (10%):\n'
735                speed = self.max_speed.tolist()
736                speed.sort()
737
738                k = 0
739                lower = min(speed)
740                for i, a in enumerate(speed):       
[4771]741                    if i % (N/10) == 0 and i != 0:
742                        # For every 10% of the sorted speeds
[4204]743                        msg += '    %d speeds in [%f, %f]\n' %(i-k, lower, a)
744                        lower = a
745                        k = i
746                       
747                msg += '    %d speeds in [%f, %f]\n'\
748                       %(N-k, lower, max(speed))                   
749               
750                     
751
752               
753           
[4200]754            # Find index of largest computed flux speed
[4836]755            if triangle_id is None:
756                k = self.k = argmax(self.max_speed)
757            else:
758                errmsg = 'Triangle_id %d does not exist in mesh: %s' %(triangle_id,
759                                                                    str(self))
760                assert 0 <= triangle_id < len(self), errmsg
761                k = self.k = triangle_id
762           
[4200]763
[4201]764            x, y = self.get_centroid_coordinates()[k]
[4702]765            radius = self.get_radii()[k]
766            area = self.get_areas()[k]     
767            max_speed = self.max_speed[k]           
[4200]768
[4376]769            msg += '  Triangle #%d with centroid (%.4f, %.4f), ' %(k, x, y)
[4702]770            msg += 'area = %.4f and radius = %.4f ' %(area, radius)
[4836]771            if triangle_id is None:
772                msg += 'had the largest computed speed: %.6f m/s ' %(max_speed)
773            else:
774                msg += 'had computed speed: %.6f m/s ' %(max_speed)
775               
[4702]776            if max_speed > 0.0:
[4376]777                msg += '(timestep=%.6f)\n' %(radius/max_speed)
778            else:
[4702]779                msg += '(timestep=%.6f)\n' %(0)     
[4201]780           
[4827]781            # Report all quantity values at vertices, edges and centroid
782            msg += '    Quantity'
[4835]783            msg += '------------\n'
[4201]784            for name in self.quantities:
785                q = self.quantities[name]
[4677]786
787                V = q.get_values(location='vertices', indices=[k])[0]
[4827]788                E = q.get_values(location='edges', indices=[k])[0]
[4677]789                C = q.get_values(location='centroids', indices=[k])                 
[4201]790               
[4827]791                s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
792                     %(name.ljust(qwidth), V[0], V[1], V[2])
[4200]793
[4827]794                s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
795                     %(name.ljust(qwidth), E[0], E[1], E[2])
796
797                s += '    %s: centroid_value = %.4f\n'\
798                     %(name.ljust(qwidth), C[0])                               
799
[4201]800                msg += s
[4200]801
[3804]802        return msg
803
804
805    def write_boundary_statistics(self, quantities = None, tags = None):
806        print self.boundary_statistics(quantities, tags)
807
808    def boundary_statistics(self, quantities = None, tags = None):
809        """Output statistics about boundary forcing at each timestep
810
811
812        Input:
[4771]813          quantities: either None, a string or a list of strings naming the
814                      quantities to be reported
815          tags:       either None, a string or a list of strings naming the
816                      tags to be reported
[3804]817
818
819        Example output:
820        Tag 'wall':
821            stage in [2, 5.5]
822            xmomentum in []
823            ymomentum in []
824        Tag 'ocean'
825
826
[4771]827        If quantities are specified only report on those. Otherwise take all
828        conserved quantities.
[3804]829        If tags are specified only report on those, otherwise take all tags.
830
831        """
832
[4704]833        # Input checks
[3804]834        import types, string
835
836        if quantities is None:
837            quantities = self.conserved_quantities
838        elif type(quantities) == types.StringType:
839            quantities = [quantities] #Turn it into a list
840
841        msg = 'Keyword argument quantities must be either None, '
842        msg += 'string or list. I got %s' %str(quantities)
843        assert type(quantities) == types.ListType, msg
844
845
846        if tags is None:
847            tags = self.get_boundary_tags()
848        elif type(tags) == types.StringType:
849            tags = [tags] #Turn it into a list
850
851        msg = 'Keyword argument tags must be either None, '
852        msg += 'string or list. I got %s' %str(tags)
853        assert type(tags) == types.ListType, msg
854
[4704]855        # Determine width of longest quantity name (for cosmetic purposes)
[3804]856        maxwidth = 0
857        for name in quantities:
858            w = len(name)
859            if w > maxwidth:
860                maxwidth = w
861
[4771]862        # Output statistics
[3804]863        msg = 'Boundary values at time %.4f:\n' %self.time
864        for tag in tags:
865            msg += '    %s:\n' %tag
866
867            for name in quantities:
868                q = self.quantities[name]
869
[4704]870                # Find range of boundary values for tag and q
[3804]871                maxval = minval = None
872                for i, ((vol_id, edge_id), B) in\
873                        enumerate(self.boundary_objects):
874                    if self.boundary[(vol_id, edge_id)] == tag:
875                        v = q.boundary_values[i]
876                        if minval is None or v < minval: minval = v
877                        if maxval is None or v > maxval: maxval = v
878
879                if minval is None or maxval is None:
880                    msg += '        Sorry no information available about' +\
881                           ' tag %s and quantity %s\n' %(tag, name)
882                else:
883                    msg += '        %s in [%12.8f, %12.8f]\n'\
884                           %(string.ljust(name, maxwidth), minval, maxval)
885
886
887        return msg
888
889
[4704]890    def update_extrema(self):
891        """Update extrema if requested by set_quantities_to_be_monitored.
892        This data is used for reporting e.g. by running
893        print domain.quantity_statistics()
894        and may also stored in output files (see data_manager in shallow_water)
895        """
[4701]896
[4711]897        # Define a tolerance for extremum computations
898        epsilon = 1.0e-6 # Import 'single_precision' from config
899       
[4704]900        if self.quantities_to_be_monitored is None:
901            return
902
903        # Observe time interval restriction if any
904        if self.monitor_time_interval is not None and\
905               (self.time < self.monitor_time_interval[0] or\
906               self.time > self.monitor_time_interval[1]):
907            return
[4711]908
[4704]909        # Update extrema for each specified quantity subject to
910        # polygon restriction (via monitor_indices).
911        for quantity_name in self.quantities_to_be_monitored:
912
913            if quantity_name in self.quantities:
914                Q = self.get_quantity(quantity_name)
915            else:
916                Q = self.create_quantity_from_expression(quantity_name)
917
918            info_block = self.quantities_to_be_monitored[quantity_name]
919
[4711]920            # Update maximum
[4771]921            # (n > None is always True, but we check explicitly because
922            # of the epsilon)
[4704]923            maxval = Q.get_maximum_value(self.monitor_indices)
[4711]924            if info_block['max'] is None or\
925                   maxval > info_block['max'] + epsilon:
[4704]926                info_block['max'] = maxval
927                maxloc = Q.get_maximum_location()
928                info_block['max_location'] = maxloc
929                info_block['max_time'] = self.time
930
931
[4711]932            # Update minimum
[4704]933            minval = Q.get_minimum_value(self.monitor_indices)
934            if info_block['min'] is None or\
[4711]935                   minval < info_block['min'] - epsilon:
[4704]936                info_block['min'] = minval               
937                minloc = Q.get_minimum_location()
938                info_block['min_location'] = minloc
939                info_block['min_time'] = self.time               
940       
941
942
943    def quantity_statistics(self, precision = '%.4f'):
[4771]944        """Return string with statistics about quantities for
945        printing or logging
[4701]946
947        Quantities reported are specified through method
948
949           set_quantities_to_be_monitored
950           
951        """
952
[4704]953        maxlen = 128 # Max length of polygon string representation
[4701]954
[4771]955        # Output statistics
[4704]956        msg = 'Monitored quantities at time %.4f:\n' %self.time
957        if self.monitor_polygon is not None:
958            p_str = str(self.monitor_polygon)
959            msg += '- Restricted by polygon: %s' %p_str[:maxlen]
960            if len(p_str) >= maxlen:
961                msg += '...\n'
962            else:
963                msg += '\n'
[4701]964
965
[4704]966        if self.monitor_time_interval is not None:
[4771]967            msg += '- Restricted by time interval: %s\n'\
968                   %str(self.monitor_time_interval)
[4704]969            time_interval_start = self.monitor_time_interval[0]
970        else:
971            time_interval_start = 0.0
[4701]972
[4704]973           
974        for quantity_name, info in self.quantities_to_be_monitored.items():
975            msg += '    %s:\n' %quantity_name
976
977            msg += '      values since time = %.2f in [%s, %s]\n'\
978                   %(time_interval_start,
979                     get_textual_float(info['min'], precision),
[4771]980                     get_textual_float(info['max'], precision))       
[4704]981                     
982            msg += '      minimum attained at time = %s, location = %s\n'\
983                   %(get_textual_float(info['min_time'], precision),
[4771]984                     get_textual_float(info['min_location'], precision))
985           
[4704]986
987            msg += '      maximum attained at time = %s, location = %s\n'\
988                   %(get_textual_float(info['max_time'], precision),
[4771]989                     get_textual_float(info['max_location'], precision))
[4704]990
991
992        return msg
993
[4712]994    def get_timestepping_method(self):
995        return self.timestepping_method
[4704]996
[4712]997    def set_timestepping_method(self,timestepping_method):
998       
[4713]999        if timestepping_method in ['euler', 'rk2', 'rk3']:
[4712]1000            self.timestepping_method = timestepping_method
1001            return
1002
1003        msg = '%s is an incorrect timestepping type'% timestepping_method
1004        raise Exception, msg
1005
[3804]1006    def get_name(self):
[3846]1007        return self.simulation_name
[3804]1008
1009    def set_name(self, name):
[3846]1010        """Assign a name to this simulation.
1011        This will be used to identify the output sww file.
[3804]1012
[3846]1013        """
1014        if name.endswith('.sww'):
[3850]1015            name = name[:-4]
[3846]1016           
1017        self.simulation_name = name
1018
[3804]1019    def get_datadir(self):
1020        return self.datadir
1021
1022    def set_datadir(self, name):
1023        self.datadir = name
1024
[4712]1025    def get_starttime(self):
1026        return self.starttime
[3804]1027
[4712]1028    def set_starttime(self, time):
1029        self.starttime = float(time)       
[3804]1030
[4712]1031
1032
[4771]1033    #--------------------------
1034    # Main components of evolve
1035    #--------------------------   
[3804]1036
1037    def evolve(self,
1038               yieldstep = None,
1039               finaltime = None,
1040               duration = None,
1041               skip_initial_step = False):
1042        """Evolve model through time starting from self.starttime.
1043
1044
1045        yieldstep: Interval between yields where results are stored,
1046                   statistics written and domain inspected or
1047                   possibly modified. If omitted the internal predefined
1048                   max timestep is used.
1049                   Internally, smaller timesteps may be taken.
1050
1051        duration: Duration of simulation
1052
[4565]1053        finaltime: Time where simulation should end. This is currently
1054        relative time.  So it's the same as duration.
[3804]1055
1056        If both duration and finaltime are given an exception is thrown.
1057
1058
1059        skip_initial_step: Boolean flag that decides whether the first
1060        yield step is skipped or not. This is useful for example to avoid
1061        duplicate steps when multiple evolve processes are dove tailed.
1062
1063
1064        Evolve is implemented as a generator and is to be called as such, e.g.
1065
1066        for t in domain.evolve(yieldstep, finaltime):
1067            <Do something with domain and t>
1068
1069
1070        All times are given in seconds
1071
1072        """
1073
1074        from anuga.config import min_timestep, max_timestep, epsilon
1075
[4704]1076        # FIXME: Maybe lump into a larger check prior to evolving
[3817]1077        msg = 'Boundary tags must be bound to boundary objects before '
1078        msg += 'evolving system, '
[3804]1079        msg += 'e.g. using the method set_boundary.\n'
1080        msg += 'This system has the boundary tags %s '\
1081               %self.get_boundary_tags()
1082        assert hasattr(self, 'boundary_objects'), msg
1083
1084
1085        if yieldstep is None:
1086            yieldstep = max_timestep
1087        else:
1088            yieldstep = float(yieldstep)
1089
1090        self._order_ = self.default_order
1091
1092
1093        if finaltime is not None and duration is not None:
[4704]1094            # print 'F', finaltime, duration
[3804]1095            msg = 'Only one of finaltime and duration may be specified'
1096            raise msg
1097        else:
1098            if finaltime is not None:
1099                self.finaltime = float(finaltime)
1100            if duration is not None:
1101                self.finaltime = self.starttime + float(duration)
1102
1103
1104
[4829]1105        N = len(self) # Number of triangles
[4704]1106        self.yieldtime = 0.0 # Track time between 'yields'
[3804]1107
[4704]1108        # Initialise interval of timestep sizes (for reporting only)
[3804]1109        self.min_timestep = max_timestep
1110        self.max_timestep = min_timestep
1111        self.number_of_steps = 0
1112        self.number_of_first_order_steps = 0
1113
[4829]1114
[4704]1115        # Update ghosts
[3804]1116        self.update_ghosts()
1117
[4704]1118        # Initial update of vertex and edge values
[3804]1119        self.distribute_to_vertices_and_edges()
1120
[4704]1121        # Update extrema if necessary (for reporting)
1122        self.update_extrema()
1123       
1124        # Initial update boundary values
[3804]1125        self.update_boundary()
1126
[4704]1127        # Or maybe restore from latest checkpoint
[3804]1128        if self.checkpoint is True:
1129            self.goto_latest_checkpoint()
1130
1131        if skip_initial_step is False:
[4704]1132            yield(self.time)  # Yield initial values
[3804]1133
1134        while True:
1135
[4712]1136            # Evolve One Step, using appropriate timestepping method
1137            if self.get_timestepping_method() == 'euler':
1138                self.evolve_one_euler_step(yieldstep,finaltime)
1139               
1140            elif self.get_timestepping_method() == 'rk2':
[4713]1141                self.evolve_one_rk2_step(yieldstep,finaltime)
[3804]1142
[4713]1143            elif self.get_timestepping_method() == 'rk3':
1144                self.evolve_one_rk3_step(yieldstep,finaltime)               
1145           
[4704]1146            # Update extrema if necessary (for reporting)
1147            self.update_extrema()           
1148
[3804]1149
1150            self.yieldtime += self.timestep
1151            self.number_of_steps += 1
1152            if self._order_ == 1:
1153                self.number_of_first_order_steps += 1
1154
[4704]1155            # Yield results
[3804]1156            if finaltime is not None and self.time >= finaltime-epsilon:
1157
1158                if self.time > finaltime:
[4704]1159                    # FIXME (Ole, 30 April 2006): Do we need this check?
[4771]1160                    # Probably not (Ole, 18 September 2008). Now changed to
1161                    # Exception
1162                    msg = 'WARNING (domain.py): time overshot finaltime. '
1163                    msg += 'Contact Ole.Nielsen@ga.gov.au'
[4735]1164                    raise Exception, msg
1165                   
[3804]1166
1167                # Yield final time and stop
1168                self.time = finaltime
1169                yield(self.time)
1170                break
1171
1172
1173            if self.yieldtime >= yieldstep:
1174                # Yield (intermediate) time and allow inspection of domain
1175
1176                if self.checkpoint is True:
1177                    self.store_checkpoint()
1178                    self.delete_old_checkpoints()
1179
[3817]1180                # Pass control on to outer loop for more specific actions
[3804]1181                yield(self.time)
1182
1183                # Reinitialise
1184                self.yieldtime = 0.0
1185                self.min_timestep = max_timestep
1186                self.max_timestep = min_timestep
1187                self.number_of_steps = 0
1188                self.number_of_first_order_steps = 0
[4829]1189                self.max_speed = zeros(N, Float)
[3804]1190
[4712]1191    def evolve_one_euler_step(self, yieldstep, finaltime):
[4721]1192        """
1193        One Euler Time Step
1194        Q^{n+1} = E(h) Q^n
1195        """
[4712]1196
1197        #Compute fluxes across each element edge
1198        self.compute_fluxes()
1199
1200        #Update timestep to fit yieldstep and finaltime
1201        self.update_timestep(yieldstep, finaltime)
1202
1203        #Update conserved quantities
1204        self.update_conserved_quantities()
1205
1206        #update ghosts
1207        self.update_ghosts()
1208
1209        #Update vertex and edge values
1210        self.distribute_to_vertices_and_edges()
1211
1212        #Update boundary values
1213        self.update_boundary()
1214
1215        #Update time
1216        self.time += self.timestep
1217
1218       
1219
1220
[4713]1221    def evolve_one_rk2_step(self, yieldstep, finaltime):
[4721]1222        """
1223        One 2nd order RK timestep
1224        Q^{n+1} = 0.5 Q^n + 0.5 E(h)^2 Q^n
1225        """
[4712]1226
1227        #Save initial initial conserved quantities values
1228        self.backup_conserved_quantities()           
1229
1230        #--------------------------------------
1231        #First euler step
1232        #--------------------------------------
1233
1234        #Compute fluxes across each element edge
1235        self.compute_fluxes()
1236
1237        #Update timestep to fit yieldstep and finaltime
1238        self.update_timestep(yieldstep, finaltime)
1239
1240        #Update conserved quantities
1241        self.update_conserved_quantities()
1242
1243        #update ghosts
1244        self.update_ghosts()
1245
1246        #Update vertex and edge values
1247        self.distribute_to_vertices_and_edges()
1248
1249        #Update boundary values
1250        self.update_boundary()
1251
1252        #Update time
1253        self.time += self.timestep
1254
1255        #------------------------------------
1256        #Second Euler step
1257        #------------------------------------
1258           
1259        #Compute fluxes across each element edge
1260        self.compute_fluxes()
1261
1262        #Update conserved quantities
1263        self.update_conserved_quantities()
1264
1265        #------------------------------------
1266        #Combine initial and final values
[4713]1267        #of conserved quantities and cleanup
[4712]1268        #------------------------------------
[4713]1269        #combine steps
[4712]1270        self.saxpy_conserved_quantities(0.5, 0.5)
[4713]1271 
1272        #update ghosts
1273        self.update_ghosts()
[4712]1274
[4713]1275        #Update vertex and edge values
1276        self.distribute_to_vertices_and_edges()
1277
1278        #Update boundary values
1279        self.update_boundary()
1280
1281
1282
1283    def evolve_one_rk3_step(self, yieldstep, finaltime):
[4721]1284        """
1285        One 3rd order RK timestep
1286        Q^(1) = 3/4 Q^n + 1/4 E(h)^2 Q^n  (at time t^n + h/2)
1287        Q^{n+1} = 1/3 Q^n + 2/3 E(h) Q^(1) (at time t^{n+1})
1288        """
[4713]1289
1290        #Save initial initial conserved quantities values
1291        self.backup_conserved_quantities()           
1292
1293        initial_time = self.time
1294       
1295        #--------------------------------------
1296        #First euler step
1297        #--------------------------------------
1298
1299        #Compute fluxes across each element edge
1300        self.compute_fluxes()
1301
1302        #Update timestep to fit yieldstep and finaltime
1303        self.update_timestep(yieldstep, finaltime)
1304
1305        #Update conserved quantities
1306        self.update_conserved_quantities()
1307
1308        #update ghosts
1309        self.update_ghosts()
1310
1311        #Update vertex and edge values
1312        self.distribute_to_vertices_and_edges()
1313
1314        #Update boundary values
1315        self.update_boundary()
1316
1317        #Update time
1318        self.time += self.timestep
1319
[4712]1320        #------------------------------------
[4713]1321        #Second Euler step
[4712]1322        #------------------------------------
1323           
[4713]1324        #Compute fluxes across each element edge
1325        self.compute_fluxes()
1326
1327        #Update conserved quantities
1328        self.update_conserved_quantities()
1329
1330        #------------------------------------
[4721]1331        #Combine steps to obtain intermediate
1332        #solution at time t^n + 0.5 h
[4713]1333        #------------------------------------
[4721]1334
[4713]1335        #combine steps
1336        self.saxpy_conserved_quantities(0.25, 0.75)
1337 
[4712]1338        #update ghosts
1339        self.update_ghosts()
1340
1341        #Update vertex and edge values
1342        self.distribute_to_vertices_and_edges()
1343
1344        #Update boundary values
1345        self.update_boundary()
1346
[4713]1347        #set substep time
1348        self.time = initial_time + self.timestep*0.5
[4712]1349
[4713]1350        #------------------------------------
1351        #Third Euler step
1352        #------------------------------------
1353           
1354        #Compute fluxes across each element edge
1355        self.compute_fluxes()
1356
1357        #Update conserved quantities
1358        self.update_conserved_quantities()
1359
1360        #------------------------------------
1361        #Combine final and initial values
[4721]1362        #and cleanup
[4713]1363        #------------------------------------
1364        #combine steps
1365        self.saxpy_conserved_quantities(2.0/3.0, 1.0/3.0)
1366 
1367        #update ghosts
1368        self.update_ghosts()
1369
1370        #Update vertex and edge values
1371        self.distribute_to_vertices_and_edges()
1372
1373        #Update boundary values
1374        self.update_boundary()
1375
[4721]1376        #set new time
[4713]1377        self.time = initial_time + self.timestep       
1378       
1379
[3804]1380    def evolve_to_end(self, finaltime = 1.0):
1381        """Iterate evolve all the way to the end
1382        """
1383
1384        for _ in self.evolve(yieldstep=None, finaltime=finaltime):
1385            pass
1386
1387
[4712]1388    def backup_conserved_quantities(self):
[4771]1389        N = len(self) # Number_of_triangles
[3804]1390
[4771]1391        # Backup conserved_quantities centroid values
[4712]1392        for name in self.conserved_quantities:
1393            Q = self.quantities[name]
1394            Q.backup_centroid_values()       
1395
1396    def saxpy_conserved_quantities(self,a,b):
1397        N = len(self) #number_of_triangles
1398
[4771]1399        # Backup conserved_quantities centroid values
[4712]1400        for name in self.conserved_quantities:
1401            Q = self.quantities[name]
1402            Q.saxpy_centroid_values(a,b)       
1403   
1404
[3804]1405    def update_boundary(self):
1406        """Go through list of boundary objects and update boundary values
1407        for all conserved quantities on boundary.
[4702]1408        It is assumed that the ordering of conserved quantities is
1409        consistent between the domain and the boundary object, i.e.
1410        the jth element of vector q must correspond to the jth conserved
1411        quantity in domain.
[3804]1412        """
1413
[4771]1414        # FIXME: Update only those that change (if that can be worked out)
1415        # FIXME: Boundary objects should not include ghost nodes.
[3804]1416        for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
[4702]1417            if B is None:
1418                print 'WARNING: Ignored boundary segment %d (None)'
1419            else:
[3804]1420                q = B.evaluate(vol_id, edge_id)
1421
1422                for j, name in enumerate(self.conserved_quantities):
1423                    Q = self.quantities[name]
1424                    Q.boundary_values[i] = q[j]
1425
1426
1427    def compute_fluxes(self):
1428        msg = 'Method compute_fluxes must be overridden by Domain subclass'
1429        raise msg
1430
1431
1432    def update_timestep(self, yieldstep, finaltime):
1433
1434        from anuga.config import min_timestep, max_timestep
1435
[4677]1436       
1437       
1438        # Protect against degenerate timesteps arising from isolated
1439        # triangles
[5242]1440        # FIXME (Steve): This should be in shallow_water as it assumes x and y
1441        # momentum
[4677]1442        if self.protect_against_isolated_degenerate_timesteps is True and\
[4805]1443               self.max_speed > 10.0: # FIXME (Ole): Make this configurable
[4677]1444
1445            # Setup 10 bins for speed histogram
1446            from anuga.utilities.numerical_tools import histogram, create_bins
1447       
1448            bins = create_bins(self.max_speed, 10)
1449            hist = histogram(self.max_speed, bins)
1450
1451            # Look for characteristic signature
1452            if len(hist) > 1 and\
1453                hist[-1] > 0 and\
1454                hist[4] == hist[5] == hist[6] == hist[7] == hist[8] == 0:
1455                    # Danger of isolated degenerate triangles
1456                    # print self.timestepping_statistics(track_speeds=True) 
1457                   
1458                    # Find triangles in last bin
1459                    # FIXME - speed up using Numeric
1460                    d = 0
1461                    for i in range(self.number_of_full_triangles):
1462                        if self.max_speed[i] > bins[-1]:
[4771]1463                            msg = 'Time=%f: Ignoring isolated high ' %self.time
1464                            msg += 'speed triangle ' 
[4677]1465                            msg += '#%d of %d with max speed=%f'\
[4771]1466                                  %(i, self.number_of_full_triangles,
1467                                    self.max_speed[i])
[4677]1468                   
[4771]1469                            # print 'Found offending triangle', i,
1470                            # self.max_speed[i]
[4677]1471                            self.get_quantity('xmomentum').set_values(0.0, indices=[i])
1472                            self.get_quantity('ymomentum').set_values(0.0, indices=[i])
1473                            self.max_speed[i]=0.0
1474                            d += 1
1475                   
1476                    #print 'Adjusted %d triangles' %d       
1477                    #print self.timestepping_statistics(track_speeds=True)     
1478               
1479
1480                       
[3804]1481        # self.timestep is calculated from speed of characteristics
1482        # Apply CFL condition here
[4713]1483        timestep = min(self.CFL*self.flux_timestep, max_timestep)
[3804]1484
[4771]1485        # Record maximal and minimal values of timestep for reporting
[3804]1486        self.max_timestep = max(timestep, self.max_timestep)
1487        self.min_timestep = min(timestep, self.min_timestep)
1488
[4677]1489           
1490 
[4771]1491        # Protect against degenerate time steps
[3804]1492        if timestep < min_timestep:
1493
[4771]1494            # Number of consecutive small steps taken b4 taking action
[3804]1495            self.smallsteps += 1
1496
1497            if self.smallsteps > self.max_smallsteps:
[4771]1498                self.smallsteps = 0 # Reset
[3804]1499
1500                if self._order_ == 1:
1501                    msg = 'WARNING: Too small timestep %.16f reached '\
1502                          %timestep
1503                    msg += 'even after %d steps of 1 order scheme'\
1504                           %self.max_smallsteps
1505                    print msg
[4771]1506                    timestep = min_timestep  # Try enforcing min_step
[3804]1507
[5080]1508                    print self.timestepping_statistics(track_speeds=True)
[4437]1509
[4677]1510                    raise Exception, msg
[3804]1511                else:
[4771]1512                    # Try to overcome situation by switching to 1 order
[3804]1513                    self._order_ = 1
1514
1515        else:
1516            self.smallsteps = 0
1517            if self._order_ == 1 and self.default_order == 2:
1518                self._order_ = 2
1519
1520
[4771]1521        # Ensure that final time is not exceeded
[3804]1522        if finaltime is not None and self.time + timestep > finaltime :
1523            timestep = finaltime-self.time
1524
[4771]1525        # Ensure that model time is aligned with yieldsteps
[3804]1526        if self.yieldtime + timestep > yieldstep:
1527            timestep = yieldstep-self.yieldtime
1528
1529        self.timestep = timestep
1530
1531
1532
1533    def compute_forcing_terms(self):
1534        """If there are any forcing functions driving the system
1535        they should be defined in Domain subclass and appended to
1536        the list self.forcing_terms
1537        """
1538
1539        for f in self.forcing_terms:
1540            f(self)
1541
1542
1543
1544    def update_conserved_quantities(self):
1545        """Update vectors of conserved quantities using previously
1546        computed fluxes specified forcing functions.
1547        """
1548
1549        from Numeric import ones, sum, equal, Float
1550
[4771]1551        N = len(self) # Number_of_triangles
[3804]1552        d = len(self.conserved_quantities)
1553
1554        timestep = self.timestep
1555
[4771]1556        # Compute forcing terms
[3804]1557        self.compute_forcing_terms()
1558
[4771]1559        # Update conserved_quantities
[3804]1560        for name in self.conserved_quantities:
1561            Q = self.quantities[name]
1562            Q.update(timestep)
1563
[4771]1564            # Note that Q.explicit_update is reset by compute_fluxes
1565            # Where is Q.semi_implicit_update reset?
1566           
[3804]1567
1568    def update_ghosts(self):
1569        pass
1570
1571    def distribute_to_vertices_and_edges(self):
1572        """Extrapolate conserved quantities from centroid to
1573        vertices and edge-midpoints for each volume
1574
1575        Default implementation is straight first order,
1576        i.e. constant values throughout each element and
1577        no reference to non-conserved quantities.
1578        """
1579
1580        for name in self.conserved_quantities:
1581            Q = self.quantities[name]
1582            if self._order_ == 1:
1583                Q.extrapolate_first_order()
1584            elif self._order_ == 2:
1585                Q.extrapolate_second_order()
[5162]1586                #Q.limit()
[3804]1587            else:
1588                raise 'Unknown order'
[5162]1589            #Q.interpolate_from_vertices_to_edges()
[3804]1590
1591
1592    def centroid_norm(self, quantity, normfunc):
1593        """Calculate the norm of the centroid values
1594        of a specific quantity, using normfunc.
1595
1596        normfunc should take a list to a float.
1597
1598        common normfuncs are provided in the module utilities.norms
1599        """
1600        return normfunc(self.quantities[quantity].centroid_values)
1601
1602
1603
[4771]1604#------------------
1605# Initialise module
1606#------------------
[3804]1607
[4771]1608# Optimisation with psyco
[3804]1609from anuga.config import use_psyco
1610if use_psyco:
1611    try:
1612        import psyco
1613    except:
1614        import os
1615        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1616            pass
[4771]1617            # Psyco isn't supported on 64 bit systems, but it doesn't matter
[3804]1618        else:
1619            msg = 'WARNING: psyco (speedup) could not import'+\
1620                  ', you may want to consider installing it'
1621            print msg
1622    else:
1623        psyco.bind(Domain.update_boundary)
[4771]1624        #psyco.bind(Domain.update_timestep) # Not worth it
[3804]1625        psyco.bind(Domain.update_conserved_quantities)
1626        psyco.bind(Domain.distribute_to_vertices_and_edges)
1627
1628
1629if __name__ == "__main__":
1630    pass
Note: See TracBrowser for help on using the repository browser.