source: trunk/anuga_core/source/anuga/abstract_2d_finite_volumes/generic_domain.py @ 8018

Last change on this file since 8018 was 8018, checked in by steve, 14 years ago

Added logging to file for fractional step operators. There needs to an member function log_timestepping_statistics() implemented for each operator.

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