source: branches/numpy/anuga/abstract_2d_finite_volumes/domain.py @ 7242

Last change on this file since 7242 was 7176, checked in by rwilson, 16 years ago

Back-merge from Numeric to numpy.

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