source: anuga_core/source/anuga/shallow_water/shallow_water_domain.py @ 7317

Last change on this file since 7317 was 7317, checked in by rwilson, 15 years ago

Replaced 'print' statements with log.critical() calls.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 89.2 KB
Line 
1"""
2Finite-volume computations of the shallow water wave equation.
3
4Title: ANGUA shallow_water_domain - 2D triangular domains for finite-volume
5       computations of the shallow water wave equation.
6
7
8Author: Ole Nielsen, Ole.Nielsen@ga.gov.au
9        Stephen Roberts, Stephen.Roberts@anu.edu.au
10        Duncan Gray, Duncan.Gray@ga.gov.au
11
12CreationDate: 2004
13
14Description:
15    This module contains a specialisation of class Domain from
16    module domain.py consisting of methods specific to the
17    Shallow Water Wave Equation
18
19    U_t + E_x + G_y = S
20
21    where
22
23    U = [w, uh, vh]
24    E = [uh, u^2h + gh^2/2, uvh]
25    G = [vh, uvh, v^2h + gh^2/2]
26    S represents source terms forcing the system
27    (e.g. gravity, friction, wind stress, ...)
28
29    and _t, _x, _y denote the derivative with respect to t, x and y
30    respectively.
31
32
33    The quantities are
34
35    symbol    variable name    explanation
36    x         x                horizontal distance from origin [m]
37    y         y                vertical distance from origin [m]
38    z         elevation        elevation of bed on which flow is modelled [m]
39    h         height           water height above z [m]
40    w         stage            absolute water level, w = z+h [m]
41    u                          speed in the x direction [m/s]
42    v                          speed in the y direction [m/s]
43    uh        xmomentum        momentum in the x direction [m^2/s]
44    vh        ymomentum        momentum in the y direction [m^2/s]
45
46    eta                        mannings friction coefficient [to appear]
47    nu                         wind stress coefficient [to appear]
48
49    The conserved quantities are w, uh, vh
50
51Reference:
52    Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
53    Christopher Zoppou and Stephen Roberts,
54    Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
55
56    Hydrodynamic modelling of coastal inundation.
57    Nielsen, O., S. Roberts, D. Gray, A. McPherson and A. Hitchman
58    In Zerger, A. and Argent, R.M. (eds) MODSIM 2005 International Congress on
59    Modelling and Simulation. Modelling and Simulation Society of Australia and
60    New Zealand, December 2005, pp. 518-523. ISBN: 0-9758400-2-9.
61    http://www.mssanz.org.au/modsim05/papers/nielsen.pdf
62
63
64SeeAlso:
65    TRAC administration of ANUGA (User Manuals etc) at
66    https://datamining.anu.edu.au/anuga and Subversion repository at
67    $HeadURL: anuga_core/source/anuga/shallow_water/shallow_water_domain.py $
68
69Constraints: See GPL license in the user guide
70Version: 1.0 ($Revision: 7317 $)
71ModifiedBy:
72    $Author: rwilson $
73    $Date: 2009-07-21 23:22:11 +0000 (Tue, 21 Jul 2009) $
74"""
75
76# Subversion keywords:
77#
78# $LastChangedDate: 2009-07-21 23:22:11 +0000 (Tue, 21 Jul 2009) $
79# $LastChangedRevision: 7317 $
80# $LastChangedBy: rwilson $
81
82
83import numpy as num
84
85from anuga.abstract_2d_finite_volumes.neighbour_mesh import segment_midpoints
86from anuga.abstract_2d_finite_volumes.domain import Domain as Generic_Domain
87from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
88     import Boundary
89from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
90     import File_boundary
91from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
92     import Dirichlet_boundary
93from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
94     import Time_boundary
95from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
96     import Transmissive_boundary
97from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
98     import AWI_boundary
99
100from anuga.pmesh.mesh_interface import create_mesh_from_regions
101from anuga.utilities.numerical_tools import gradient, mean, ensure_numeric
102from anuga.geospatial_data.geospatial_data import ensure_geospatial
103
104from anuga.config import minimum_storable_height
105from anuga.config import minimum_allowed_height, maximum_allowed_speed
106from anuga.config import g, epsilon, beta_w, beta_w_dry,\
107     beta_uh, beta_uh_dry, beta_vh, beta_vh_dry, tight_slope_limiters
108from anuga.config import alpha_balance
109from anuga.config import optimise_dry_cells
110from anuga.config import optimised_gradient_limiter
111from anuga.config import use_edge_limiter
112from anuga.config import use_centroid_velocities
113from anuga.config import netcdf_mode_r, netcdf_mode_w, netcdf_mode_a
114
115from anuga.fit_interpolate.interpolate import Modeltime_too_late, \
116                                              Modeltime_too_early
117
118from anuga.utilities.polygon import inside_polygon, polygon_area, \
119                                    is_inside_polygon
120import anuga.utilities.log as log
121
122from types import IntType, FloatType
123from warnings import warn
124
125
126################################################################################
127# Shallow water domain
128################################################################################
129
130##
131# @brief Class for a shallow water domain.
132class Domain(Generic_Domain):
133
134    conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
135    other_quantities = ['elevation', 'friction']
136
137    ##
138    # @brief Instantiate a shallow water domain.
139    # @param coordinates
140    # @param vertices
141    # @param boundary
142    # @param tagged_elements
143    # @param geo_reference
144    # @param use_inscribed_circle
145    # @param mesh_filename
146    # @param use_cache
147    # @param verbose
148    # @param full_send_dict
149    # @param ghost_recv_dict
150    # @param processor
151    # @param numproc
152    # @param number_of_full_nodes
153    # @param number_of_full_triangles
154    def __init__(self,
155                 coordinates=None,
156                 vertices=None,
157                 boundary=None,
158                 tagged_elements=None,
159                 geo_reference=None,
160                 use_inscribed_circle=False,
161                 mesh_filename=None,
162                 use_cache=False,
163                 verbose=False,
164                 full_send_dict=None,
165                 ghost_recv_dict=None,
166                 processor=0,
167                 numproc=1,
168                 number_of_full_nodes=None,
169                 number_of_full_triangles=None):
170
171        other_quantities = ['elevation', 'friction']
172        Generic_Domain.__init__(self,
173                                coordinates,
174                                vertices,
175                                boundary,
176                                Domain.conserved_quantities,
177                                Domain.other_quantities,
178                                tagged_elements,
179                                geo_reference,
180                                use_inscribed_circle,
181                                mesh_filename,
182                                use_cache,
183                                verbose,
184                                full_send_dict,
185                                ghost_recv_dict,
186                                processor,
187                                numproc,
188                                number_of_full_nodes=number_of_full_nodes,
189                                number_of_full_triangles=number_of_full_triangles)
190
191        self.set_minimum_allowed_height(minimum_allowed_height)
192
193        self.maximum_allowed_speed = maximum_allowed_speed
194        self.g = g
195        self.beta_w = beta_w
196        self.beta_w_dry = beta_w_dry
197        self.beta_uh = beta_uh
198        self.beta_uh_dry = beta_uh_dry
199        self.beta_vh = beta_vh
200        self.beta_vh_dry = beta_vh_dry
201        self.alpha_balance = alpha_balance
202
203        self.tight_slope_limiters = tight_slope_limiters
204        self.optimise_dry_cells = optimise_dry_cells
205
206        self.forcing_terms.append(manning_friction_implicit)
207        self.forcing_terms.append(gravity)
208
209        # Stored output
210        self.store = True
211        self.format = 'sww'
212        self.set_store_vertices_uniquely(False)
213        self.minimum_storable_height = minimum_storable_height
214        self.quantities_to_be_stored = ['stage', 'xmomentum', 'ymomentum']
215
216        # Limiters
217        self.use_edge_limiter = use_edge_limiter
218        self.optimised_gradient_limiter = optimised_gradient_limiter
219        self.use_centroid_velocities = use_centroid_velocities
220
221    ##
222    # @brief
223    # @param beta
224    def set_all_limiters(self, beta):
225        """Shorthand to assign one constant value [0,1] to all limiters.
226        0 Corresponds to first order, where as larger values make use of
227        the second order scheme.
228        """
229
230        self.beta_w = beta
231        self.beta_w_dry = beta
232        self.quantities['stage'].beta = beta
233
234        self.beta_uh = beta
235        self.beta_uh_dry = beta
236        self.quantities['xmomentum'].beta = beta
237
238        self.beta_vh = beta
239        self.beta_vh_dry = beta
240        self.quantities['ymomentum'].beta = beta
241
242    ##
243    # @brief
244    # @param flag
245    # @param reduction
246    def set_store_vertices_uniquely(self, flag, reduction=None):
247        """Decide whether vertex values should be stored uniquely as
248        computed in the model (True) or whether they should be reduced to one
249        value per vertex using self.reduction (False).
250        """
251
252        # FIXME (Ole): how about using the word "continuous vertex values" or
253        # "continuous stage surface"
254        self.smooth = not flag
255
256        # Reduction operation for get_vertex_values
257        if reduction is None:
258            self.reduction = mean
259            #self.reduction = min  #Looks better near steep slopes
260
261    ##
262    # @brief Set the minimum depth that will be written to an SWW file.
263    # @param minimum_storable_height The minimum stored height (in m).
264    def set_minimum_storable_height(self, minimum_storable_height):
265        """Set the minimum depth that will be recognised when writing
266        to an sww file. This is useful for removing thin water layers
267        that seems to be caused by friction creep.
268
269        The minimum allowed sww depth is in meters.
270        """
271
272        self.minimum_storable_height = minimum_storable_height
273
274    ##
275    # @brief
276    # @param minimum_allowed_height
277    def set_minimum_allowed_height(self, minimum_allowed_height):
278        """Set minimum depth that will be recognised in the numerical scheme.
279
280        The minimum allowed depth is in meters.
281
282        The parameter H0 (Minimal height for flux computation)
283        is also set by this function
284        """
285
286        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
287
288        #FIXME (Ole): Maybe use histogram to identify isolated extreme speeds
289        #and deal with them adaptively similarly to how we used to use 1 order
290        #steps to recover.
291
292        self.minimum_allowed_height = minimum_allowed_height
293        self.H0 = minimum_allowed_height
294
295    ##
296    # @brief
297    # @param maximum_allowed_speed
298    def set_maximum_allowed_speed(self, maximum_allowed_speed):
299        """Set the maximum particle speed that is allowed in water
300        shallower than minimum_allowed_height. This is useful for
301        controlling speeds in very thin layers of water and at the same time
302        allow some movement avoiding pooling of water.
303        """
304
305        self.maximum_allowed_speed = maximum_allowed_speed
306
307    ##
308    # @brief
309    # @param points_file_block_line_size
310    def set_points_file_block_line_size(self, points_file_block_line_size):
311        """Set the minimum depth that will be recognised when writing
312        to an sww file. This is useful for removing thin water layers
313        that seems to be caused by friction creep.
314
315        The minimum allowed sww depth is in meters.
316        """
317        self.points_file_block_line_size = points_file_block_line_size
318
319    ##
320    # @brief Set the quantities that will be written to an SWW file.
321    # @param q The quantities to be written.
322    # @note Param 'q' may be None, single quantity or list of quantity strings.
323    # @note If 'q' is None, no quantities will be stored in the SWW file.
324    def set_quantities_to_be_stored(self, q):
325        """Specify which quantities will be stored in the sww file.
326
327        q must be either:
328          - the name of a quantity
329          - a list of quantity names
330          - None
331
332        In the two first cases, the named quantities will be stored at
333        each yieldstep (This is in addition to the quantities elevation
334        and friction)
335
336        If q is None, storage will be switched off altogether.
337        """
338
339        if q is None:
340            self.quantities_to_be_stored = []
341            self.store = False
342            return
343
344        if isinstance(q, basestring):
345            q = [q] # Turn argument into a list
346
347        # Check correcness
348        for quantity_name in q:
349            msg = ('Quantity %s is not a valid conserved quantity'
350                   % quantity_name)
351            assert quantity_name in self.conserved_quantities, msg
352
353        self.quantities_to_be_stored = q
354
355    ##
356    # @brief
357    # @param indices
358    def get_wet_elements(self, indices=None):
359        """Return indices for elements where h > minimum_allowed_height
360
361        Optional argument:
362            indices is the set of element ids that the operation applies to.
363
364        Usage:
365            indices = get_wet_elements()
366
367        Note, centroid values are used for this operation
368        """
369
370        # Water depth below which it is considered to be 0 in the model
371        # FIXME (Ole): Allow this to be specified as a keyword argument as well
372        from anuga.config import minimum_allowed_height
373
374        elevation = self.get_quantity('elevation').\
375                        get_values(location='centroids', indices=indices)
376        stage = self.get_quantity('stage').\
377                    get_values(location='centroids', indices=indices)
378        depth = stage - elevation
379
380        # Select indices for which depth > 0
381        wet_indices = num.compress(depth > minimum_allowed_height,
382                                   num.arange(len(depth)))
383        return wet_indices
384
385    ##
386    # @brief
387    # @param indices
388    def get_maximum_inundation_elevation(self, indices=None):
389        """Return highest elevation where h > 0
390
391        Optional argument:
392            indices is the set of element ids that the operation applies to.
393
394        Usage:
395            q = get_maximum_inundation_elevation()
396
397        Note, centroid values are used for this operation
398        """
399
400        wet_elements = self.get_wet_elements(indices)
401        return self.get_quantity('elevation').\
402                   get_maximum_value(indices=wet_elements)
403
404    ##
405    # @brief
406    # @param indices
407    def get_maximum_inundation_location(self, indices=None):
408        """Return location of highest elevation where h > 0
409
410        Optional argument:
411            indices is the set of element ids that the operation applies to.
412
413        Usage:
414            q = get_maximum_inundation_location()
415
416        Note, centroid values are used for this operation
417        """
418
419        wet_elements = self.get_wet_elements(indices)
420        return self.get_quantity('elevation').\
421                   get_maximum_location(indices=wet_elements)
422
423    ##
424    # @brief Get the total flow through an arbitrary poly line.
425    # @param polyline Representation of desired cross section.
426    # @param verbose True if this method is to be verbose.
427    # @note 'polyline' may contain multiple sections allowing complex shapes.
428    # @note Assume absolute UTM coordinates.
429    def get_flow_through_cross_section(self, polyline, verbose=False):
430        """Get the total flow through an arbitrary poly line.
431
432        This is a run-time equivalent of the function with same name
433        in data_manager.py
434
435        Input:
436            polyline: Representation of desired cross section - it may contain
437                      multiple sections allowing for complex shapes. Assume
438                      absolute UTM coordinates.
439                      Format [[x0, y0], [x1, y1], ...]
440
441        Output:
442            Q: Total flow [m^3/s] across given segments.
443        """
444
445        # Find all intersections and associated triangles.
446        segments = self.get_intersecting_segments(polyline, use_cache=True,
447                                                  verbose=verbose)
448
449        # Get midpoints
450        midpoints = segment_midpoints(segments)
451
452        # Make midpoints Geospatial instances
453        midpoints = ensure_geospatial(midpoints, self.geo_reference)
454
455        # Compute flow
456        if verbose:
457            log.critical('Computing flow through specified cross section')
458
459        # Get interpolated values
460        xmomentum = self.get_quantity('xmomentum')
461        ymomentum = self.get_quantity('ymomentum')
462
463        uh = xmomentum.get_values(interpolation_points=midpoints,
464                                  use_cache=True)
465        vh = ymomentum.get_values(interpolation_points=midpoints,
466                                  use_cache=True)
467
468        # Compute and sum flows across each segment
469        total_flow = 0
470        for i in range(len(uh)):
471            # Inner product of momentum vector with segment normal [m^2/s]
472            normal = segments[i].normal
473            normal_momentum = uh[i]*normal[0] + vh[i]*normal[1]
474
475            # Flow across this segment [m^3/s]
476            segment_flow = normal_momentum*segments[i].length
477
478            # Accumulate
479            total_flow += segment_flow
480
481        return total_flow
482
483    ##
484    # @brief
485    # @param polyline Representation of desired cross section.
486    # @param kind Select energy type to compute ('specific' or 'total').
487    # @param verbose True if this method is to be verbose.
488    # @note 'polyline' may contain multiple sections allowing complex shapes.
489    # @note Assume absolute UTM coordinates.
490    def get_energy_through_cross_section(self, polyline,
491                                         kind='total',
492                                         verbose=False):
493        """Obtain average energy head [m] across specified cross section.
494
495        Inputs:
496            polyline: Representation of desired cross section - it may contain
497                      multiple sections allowing for complex shapes. Assume
498                      absolute UTM coordinates.
499                      Format [[x0, y0], [x1, y1], ...]
500            kind:     Select which energy to compute.
501                      Options are 'specific' and 'total' (default)
502
503        Output:
504            E: Average energy [m] across given segments for all stored times.
505
506        The average velocity is computed for each triangle intersected by
507        the polyline and averaged weighted by segment lengths.
508
509        The typical usage of this function would be to get average energy of
510        flow in a channel, and the polyline would then be a cross section
511        perpendicular to the flow.
512
513        #FIXME (Ole) - need name for this energy reflecting that its dimension
514        is [m].
515        """
516
517        from anuga.config import g, epsilon, velocity_protection as h0
518
519        # Find all intersections and associated triangles.
520        segments = self.get_intersecting_segments(polyline, use_cache=True,
521                                                  verbose=verbose)
522
523        # Get midpoints
524        midpoints = segment_midpoints(segments)
525
526        # Make midpoints Geospatial instances
527        midpoints = ensure_geospatial(midpoints, self.geo_reference)
528
529        # Compute energy
530        if verbose: log.critical('Computing %s energy' % kind)
531
532        # Get interpolated values
533        stage = self.get_quantity('stage')
534        elevation = self.get_quantity('elevation')
535        xmomentum = self.get_quantity('xmomentum')
536        ymomentum = self.get_quantity('ymomentum')
537
538        w = stage.get_values(interpolation_points=midpoints, use_cache=True)
539        z = elevation.get_values(interpolation_points=midpoints, use_cache=True)
540        uh = xmomentum.get_values(interpolation_points=midpoints,
541                                  use_cache=True)
542        vh = ymomentum.get_values(interpolation_points=midpoints,
543                                  use_cache=True)
544        h = w-z                # Depth
545
546        # Compute total length of polyline for use with weighted averages
547        total_line_length = 0.0
548        for segment in segments:
549            total_line_length += segment.length
550
551        # Compute and sum flows across each segment
552        average_energy = 0.0
553        for i in range(len(w)):
554            # Average velocity across this segment
555            if h[i] > epsilon:
556                # Use protection against degenerate velocities
557                u = uh[i]/(h[i] + h0/h[i])
558                v = vh[i]/(h[i] + h0/h[i])
559            else:
560                u = v = 0.0
561
562            speed_squared = u*u + v*v
563            kinetic_energy = 0.5*speed_squared/g
564
565            if kind == 'specific':
566                segment_energy = h[i] + kinetic_energy
567            elif kind == 'total':
568                segment_energy = w[i] + kinetic_energy
569            else:
570                msg = 'Energy kind must be either "specific" or "total".'
571                msg += ' I got %s' %kind
572
573            # Add to weighted average
574            weigth = segments[i].length/total_line_length
575            average_energy += segment_energy*weigth
576
577        return average_energy
578
579    ##
580    # @brief Run integrity checks on shallow water domain.
581    def check_integrity(self):
582        Generic_Domain.check_integrity(self)
583
584        #Check that we are solving the shallow water wave equation
585        msg = 'First conserved quantity must be "stage"'
586        assert self.conserved_quantities[0] == 'stage', msg
587        msg = 'Second conserved quantity must be "xmomentum"'
588        assert self.conserved_quantities[1] == 'xmomentum', msg
589        msg = 'Third conserved quantity must be "ymomentum"'
590        assert self.conserved_quantities[2] == 'ymomentum', msg
591
592    ##
593    # @brief
594    def extrapolate_second_order_sw(self):
595        #Call correct module function (either from this module or C-extension)
596        extrapolate_second_order_sw(self)
597
598    ##
599    # @brief
600    def compute_fluxes(self):
601        #Call correct module function (either from this module or C-extension)
602        compute_fluxes(self)
603
604    ##
605    # @brief
606    def distribute_to_vertices_and_edges(self):
607        # Call correct module function
608        if self.use_edge_limiter:
609            distribute_using_edge_limiter(self)
610        else:
611            distribute_using_vertex_limiter(self)
612
613    ##
614    # @brief Evolve the model by one step.
615    # @param yieldstep
616    # @param finaltime
617    # @param duration
618    # @param skip_initial_step
619    def evolve(self,
620               yieldstep=None,
621               finaltime=None,
622               duration=None,
623               skip_initial_step=False):
624        """Specialisation of basic evolve method from parent class"""
625
626        # Call check integrity here rather than from user scripts
627        # self.check_integrity()
628
629        msg = 'Attribute self.beta_w must be in the interval [0, 2]'
630        assert 0 <= self.beta_w <= 2.0, msg
631
632        # Initial update of vertex and edge values before any STORAGE
633        # and or visualisation.
634        # This is done again in the initialisation of the Generic_Domain
635        # evolve loop but we do it here to ensure the values are ok for storage.
636        self.distribute_to_vertices_and_edges()
637
638        if self.store is True and self.time == 0.0:
639            self.initialise_storage()
640        else:
641            pass
642            # print 'Results will not be stored.'
643            # print 'To store results set domain.store = True'
644            # FIXME: Diagnostic output should be controlled by
645            # a 'verbose' flag living in domain (or in a parent class)
646
647        # Call basic machinery from parent class
648        for t in Generic_Domain.evolve(self, yieldstep=yieldstep,
649                                       finaltime=finaltime, duration=duration,
650                                       skip_initial_step=skip_initial_step):
651            # Store model data, e.g. for subsequent visualisation
652            if self.store is True:
653                self.store_timestep(self.quantities_to_be_stored)
654
655            # FIXME: Could maybe be taken from specified list
656            # of 'store every step' quantities
657
658            # Pass control on to outer loop for more specific actions
659            yield(t)
660
661    ##
662    # @brief
663    def initialise_storage(self):
664        """Create and initialise self.writer object for storing data.
665        Also, save x,y and bed elevation
666        """
667
668        from anuga.shallow_water.data_manager import get_dataobject
669
670        # Initialise writer
671        self.writer = get_dataobject(self, mode=netcdf_mode_w)
672
673        # Store vertices and connectivity
674        self.writer.store_connectivity()
675
676    ##
677    # @brief
678    # @param name
679    def store_timestep(self, name):
680        """Store named quantity and time.
681
682        Precondition:
683           self.write has been initialised
684        """
685
686        self.writer.store_timestep(name)
687
688    ##
689    # @brief Get time stepping statistics string for printing.
690    # @param track_speeds
691    # @param triangle_id
692    def timestepping_statistics(self,
693                                track_speeds=False,
694                                triangle_id=None):
695        """Return string with time stepping statistics for printing or logging
696
697        Optional boolean keyword track_speeds decides whether to report
698        location of smallest timestep as well as a histogram and percentile
699        report.
700        """
701
702        from anuga.config import epsilon, g
703
704        # Call basic machinery from parent class
705        msg = Generic_Domain.timestepping_statistics(self, track_speeds,
706                                                     triangle_id)
707
708        if track_speeds is True:
709            # qwidth determines the text field used for quantities
710            qwidth = self.qwidth
711
712            # Selected triangle
713            k = self.k
714
715            # Report some derived quantities at vertices, edges and centroid
716            # specific to the shallow water wave equation
717            z = self.quantities['elevation']
718            w = self.quantities['stage']
719
720            Vw = w.get_values(location='vertices', indices=[k])[0]
721            Ew = w.get_values(location='edges', indices=[k])[0]
722            Cw = w.get_values(location='centroids', indices=[k])
723
724            Vz = z.get_values(location='vertices', indices=[k])[0]
725            Ez = z.get_values(location='edges', indices=[k])[0]
726            Cz = z.get_values(location='centroids', indices=[k])
727
728            name = 'depth'
729            Vh = Vw-Vz
730            Eh = Ew-Ez
731            Ch = Cw-Cz
732
733            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
734                 %(name.ljust(qwidth), Vh[0], Vh[1], Vh[2])
735
736            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
737                 %(name.ljust(qwidth), Eh[0], Eh[1], Eh[2])
738
739            s += '    %s: centroid_value = %.4f\n'\
740                 %(name.ljust(qwidth), Ch[0])
741
742            msg += s
743
744            uh = self.quantities['xmomentum']
745            vh = self.quantities['ymomentum']
746
747            Vuh = uh.get_values(location='vertices', indices=[k])[0]
748            Euh = uh.get_values(location='edges', indices=[k])[0]
749            Cuh = uh.get_values(location='centroids', indices=[k])
750
751            Vvh = vh.get_values(location='vertices', indices=[k])[0]
752            Evh = vh.get_values(location='edges', indices=[k])[0]
753            Cvh = vh.get_values(location='centroids', indices=[k])
754
755            # Speeds in each direction
756            Vu = Vuh/(Vh + epsilon)
757            Eu = Euh/(Eh + epsilon)
758            Cu = Cuh/(Ch + epsilon)
759            name = 'U'
760            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
761                 %(name.ljust(qwidth), Vu[0], Vu[1], Vu[2])
762
763            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
764                 %(name.ljust(qwidth), Eu[0], Eu[1], Eu[2])
765
766            s += '    %s: centroid_value = %.4f\n'\
767                 %(name.ljust(qwidth), Cu[0])
768
769            msg += s
770
771            Vv = Vvh/(Vh + epsilon)
772            Ev = Evh/(Eh + epsilon)
773            Cv = Cvh/(Ch + epsilon)
774            name = 'V'
775            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
776                 %(name.ljust(qwidth), Vv[0], Vv[1], Vv[2])
777
778            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
779                 %(name.ljust(qwidth), Ev[0], Ev[1], Ev[2])
780
781            s += '    %s: centroid_value = %.4f\n'\
782                 %(name.ljust(qwidth), Cv[0])
783
784            msg += s
785
786            # Froude number in each direction
787            name = 'Froude (x)'
788            Vfx = Vu/(num.sqrt(g*Vh) + epsilon)
789            Efx = Eu/(num.sqrt(g*Eh) + epsilon)
790            Cfx = Cu/(num.sqrt(g*Ch) + epsilon)
791
792            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
793                 %(name.ljust(qwidth), Vfx[0], Vfx[1], Vfx[2])
794
795            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
796                 %(name.ljust(qwidth), Efx[0], Efx[1], Efx[2])
797
798            s += '    %s: centroid_value = %.4f\n'\
799                 %(name.ljust(qwidth), Cfx[0])
800
801            msg += s
802
803            name = 'Froude (y)'
804            Vfy = Vv/(num.sqrt(g*Vh) + epsilon)
805            Efy = Ev/(num.sqrt(g*Eh) + epsilon)
806            Cfy = Cv/(num.sqrt(g*Ch) + epsilon)
807
808            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
809                 %(name.ljust(qwidth), Vfy[0], Vfy[1], Vfy[2])
810
811            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
812                 %(name.ljust(qwidth), Efy[0], Efy[1], Efy[2])
813
814            s += '    %s: centroid_value = %.4f\n'\
815                 %(name.ljust(qwidth), Cfy[0])
816
817            msg += s
818
819        return msg
820       
821       
822
823    def compute_boundary_flows(self):
824        """Compute boundary flows at current timestep.
825                       
826        Quantities computed are:
827           Total inflow across boundary
828           Total outflow across boundary
829           Flow across each tagged boundary segment
830        """
831               
832        # Run through boundary array and compute for each segment
833        # the normal momentum ((uh, vh) dot normal) times segment length.
834        # Based on sign accumulate this into boundary_inflow and boundary_outflow.
835                       
836        # Compute flows along boundary
837       
838        uh = self.get_quantity('xmomentum').get_values(location='edges')
839        vh = self.get_quantity('ymomentum').get_values(location='edges')       
840       
841        # Loop through edges that lie on the boundary and calculate
842        # flows
843        boundary_flows = {}
844        total_boundary_inflow = 0.0
845        total_boundary_outflow = 0.0
846        for vol_id, edge_id in self.boundary:
847            # Compute normal flow across edge. Since normal vector points
848            # away from triangle, a positive sign means that water
849            # flows *out* from this triangle.
850             
851            momentum = [uh[vol_id, edge_id], vh[vol_id, edge_id]]
852            normal = self.mesh.get_normal(vol_id, edge_id)
853            length = self.mesh.get_edgelength(vol_id, edge_id)           
854            normal_flow = num.dot(momentum, normal)*length
855           
856            # Reverse sign so that + is taken to mean inflow
857            # and - means outflow. This is more intuitive.
858            edge_flow = -normal_flow
859           
860            # Tally up inflows and outflows separately
861            if edge_flow > 0:
862                # Flow is inflow     
863                total_boundary_inflow += edge_flow                                 
864            else:
865                # Flow is outflow
866                total_boundary_outflow += edge_flow   
867
868            # Tally up flows by boundary tag
869            tag = self.boundary[(vol_id, edge_id)]
870           
871            if tag not in boundary_flows:
872                boundary_flows[tag] = 0.0
873            boundary_flows[tag] += edge_flow
874           
875               
876        return boundary_flows, total_boundary_inflow, total_boundary_outflow
877       
878
879    def compute_forcing_flows(self):
880        """Compute flows in and out of domain due to forcing terms.
881                       
882        Quantities computed are:
883               
884       
885           Total inflow through forcing terms
886           Total outflow through forcing terms
887           Current total volume in domain       
888
889        """
890
891        #FIXME(Ole): We need to separate what part of explicit_update was
892        # due to the normal flux calculations and what is due to forcing terms.
893       
894        pass
895                       
896       
897    def compute_total_volume(self):
898        """Compute total volume (m^3) of water in entire domain
899        """
900       
901        area = self.mesh.get_areas()
902        volume = 0.0
903       
904        stage = self.get_quantity('stage').get_values(location='centroids')
905        elevation = self.get_quantity('elevation').get_values(location='centroids')       
906        depth = stage-elevation
907       
908        return num.sum(depth*area)
909       
910       
911    def volumetric_balance_statistics(self):               
912        """Create volumetric balance report suitable for printing or logging.
913        """
914       
915        (boundary_flows, total_boundary_inflow,
916         total_boundary_outflow) = self.compute_boundary_flows() 
917       
918        s = '---------------------------\n'       
919        s += 'Volumetric balance report:\n'
920        s += '--------------------------\n'
921        s += 'Total boundary inflow [m^3/s]: %.2f\n' % total_boundary_inflow
922        s += 'Total boundary outflow [m^3/s]: %.2f\n' % total_boundary_outflow       
923        s += 'Net boundary flow by tags [m^3/s]\n'
924        for tag in boundary_flows:
925            s += '    %s [m^3/s]: %.2f\n' % (tag, boundary_flows[tag])
926       
927        s += 'Total net boundary flow [m^3/s]: %.2f\n' % (total_boundary_inflow + total_boundary_outflow) 
928        s += 'Total volume in domain [m^3]: %.2f\n' % self.compute_total_volume()
929       
930        # The go through explicit forcing update and record the rate of change for stage and
931        # record into forcing_inflow and forcing_outflow. Finally compute integral
932        # of depth to obtain total volume of domain.
933       
934        # FIXME(Ole): This part is not yet done.               
935       
936        return s       
937           
938################################################################################
939# End of class Shallow Water Domain
940################################################################################
941
942#-----------------
943# Flux computation
944#-----------------
945
946## @brief Compute fluxes and timestep suitable for all volumes in domain.
947# @param domain The domain to calculate fluxes for.
948def compute_fluxes(domain):
949    """Compute fluxes and timestep suitable for all volumes in domain.
950
951    Compute total flux for each conserved quantity using "flux_function"
952
953    Fluxes across each edge are scaled by edgelengths and summed up
954    Resulting flux is then scaled by area and stored in
955    explicit_update for each of the three conserved quantities
956    stage, xmomentum and ymomentum
957
958    The maximal allowable speed computed by the flux_function for each volume
959    is converted to a timestep that must not be exceeded. The minimum of
960    those is computed as the next overall timestep.
961
962    Post conditions:
963      domain.explicit_update is reset to computed flux values
964      domain.timestep is set to the largest step satisfying all volumes.
965
966    This wrapper calls the underlying C version of compute fluxes
967    """
968
969    import sys
970    from shallow_water_ext import compute_fluxes_ext_central \
971                                  as compute_fluxes_ext
972
973    N = len(domain)    # number_of_triangles
974
975    # Shortcuts
976    Stage = domain.quantities['stage']
977    Xmom = domain.quantities['xmomentum']
978    Ymom = domain.quantities['ymomentum']
979    Bed = domain.quantities['elevation']
980
981    timestep = float(sys.maxint)
982
983    flux_timestep = compute_fluxes_ext(timestep,
984                                       domain.epsilon,
985                                       domain.H0,
986                                       domain.g,
987                                       domain.neighbours,
988                                       domain.neighbour_edges,
989                                       domain.normals,
990                                       domain.edgelengths,
991                                       domain.radii,
992                                       domain.areas,
993                                       domain.tri_full_flag,
994                                       Stage.edge_values,
995                                       Xmom.edge_values,
996                                       Ymom.edge_values,
997                                       Bed.edge_values,
998                                       Stage.boundary_values,
999                                       Xmom.boundary_values,
1000                                       Ymom.boundary_values,
1001                                       Stage.explicit_update,
1002                                       Xmom.explicit_update,
1003                                       Ymom.explicit_update,
1004                                       domain.already_computed_flux,
1005                                       domain.max_speed,
1006                                       int(domain.optimise_dry_cells))
1007
1008    domain.flux_timestep = flux_timestep
1009
1010################################################################################
1011# Module functions for gradient limiting
1012################################################################################
1013
1014##
1015# @brief Wrapper for C version of extrapolate_second_order_sw.
1016# @param domain The domain to operate on.
1017# @note MH090605 The following method belongs to the shallow_water domain class
1018#       see comments in the corresponding method in shallow_water_ext.c
1019def extrapolate_second_order_sw(domain):
1020    """Wrapper calling C version of extrapolate_second_order_sw"""
1021
1022    import sys
1023    from shallow_water_ext import extrapolate_second_order_sw as extrapol2
1024
1025    N = len(domain) # number_of_triangles
1026
1027    # Shortcuts
1028    Stage = domain.quantities['stage']
1029    Xmom = domain.quantities['xmomentum']
1030    Ymom = domain.quantities['ymomentum']
1031    Elevation = domain.quantities['elevation']
1032
1033    extrapol2(domain,
1034              domain.surrogate_neighbours,
1035              domain.number_of_boundaries,
1036              domain.centroid_coordinates,
1037              Stage.centroid_values,
1038              Xmom.centroid_values,
1039              Ymom.centroid_values,
1040              Elevation.centroid_values,
1041              domain.vertex_coordinates,
1042              Stage.vertex_values,
1043              Xmom.vertex_values,
1044              Ymom.vertex_values,
1045              Elevation.vertex_values,
1046              int(domain.optimise_dry_cells))
1047
1048##
1049# @brief Distribution from centroids to vertices specific to the SWW eqn.
1050# @param domain The domain to operate on.
1051def distribute_using_vertex_limiter(domain):
1052    """Distribution from centroids to vertices specific to the SWW equation.
1053
1054    It will ensure that h (w-z) is always non-negative even in the
1055    presence of steep bed-slopes by taking a weighted average between shallow
1056    and deep cases.
1057
1058    In addition, all conserved quantities get distributed as per either a
1059    constant (order==1) or a piecewise linear function (order==2).
1060
1061    FIXME: more explanation about removal of artificial variability etc
1062
1063    Precondition:
1064      All quantities defined at centroids and bed elevation defined at
1065      vertices.
1066
1067    Postcondition
1068      Conserved quantities defined at vertices
1069    """
1070
1071    # Remove very thin layers of water
1072    protect_against_infinitesimal_and_negative_heights(domain)
1073
1074    # Extrapolate all conserved quantities
1075    if domain.optimised_gradient_limiter:
1076        # MH090605 if second order,
1077        # perform the extrapolation and limiting on
1078        # all of the conserved quantities
1079
1080        if (domain._order_ == 1):
1081            for name in domain.conserved_quantities:
1082                Q = domain.quantities[name]
1083                Q.extrapolate_first_order()
1084        elif domain._order_ == 2:
1085            domain.extrapolate_second_order_sw()
1086        else:
1087            raise 'Unknown order'
1088    else:
1089        # Old code:
1090        for name in domain.conserved_quantities:
1091            Q = domain.quantities[name]
1092
1093            if domain._order_ == 1:
1094                Q.extrapolate_first_order()
1095            elif domain._order_ == 2:
1096                Q.extrapolate_second_order_and_limit_by_vertex()
1097            else:
1098                raise 'Unknown order'
1099
1100    # Take bed elevation into account when water heights are small
1101    balance_deep_and_shallow(domain)
1102
1103    # Compute edge values by interpolation
1104    for name in domain.conserved_quantities:
1105        Q = domain.quantities[name]
1106        Q.interpolate_from_vertices_to_edges()
1107
1108##
1109# @brief Distribution from centroids to edges specific to the SWW eqn.
1110# @param domain The domain to operate on.
1111def distribute_using_edge_limiter(domain):
1112    """Distribution from centroids to edges specific to the SWW eqn.
1113
1114    It will ensure that h (w-z) is always non-negative even in the
1115    presence of steep bed-slopes by taking a weighted average between shallow
1116    and deep cases.
1117
1118    In addition, all conserved quantities get distributed as per either a
1119    constant (order==1) or a piecewise linear function (order==2).
1120
1121
1122    Precondition:
1123      All quantities defined at centroids and bed elevation defined at
1124      vertices.
1125
1126    Postcondition
1127      Conserved quantities defined at vertices
1128    """
1129
1130    # Remove very thin layers of water
1131    protect_against_infinitesimal_and_negative_heights(domain)
1132
1133    for name in domain.conserved_quantities:
1134        Q = domain.quantities[name]
1135        if domain._order_ == 1:
1136            Q.extrapolate_first_order()
1137        elif domain._order_ == 2:
1138            Q.extrapolate_second_order_and_limit_by_edge()
1139        else:
1140            raise 'Unknown order'
1141
1142    balance_deep_and_shallow(domain)
1143
1144    # Compute edge values by interpolation
1145    for name in domain.conserved_quantities:
1146        Q = domain.quantities[name]
1147        Q.interpolate_from_vertices_to_edges()
1148
1149##
1150# @brief  Protect against infinitesimal heights and associated high velocities.
1151# @param domain The domain to operate on.
1152def protect_against_infinitesimal_and_negative_heights(domain):
1153    """Protect against infinitesimal heights and associated high velocities"""
1154
1155    from shallow_water_ext import protect
1156
1157    # Shortcuts
1158    wc = domain.quantities['stage'].centroid_values
1159    zc = domain.quantities['elevation'].centroid_values
1160    xmomc = domain.quantities['xmomentum'].centroid_values
1161    ymomc = domain.quantities['ymomentum'].centroid_values
1162
1163    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
1164            domain.epsilon, wc, zc, xmomc, ymomc)
1165
1166##
1167# @brief Wrapper for C function balance_deep_and_shallow_c().
1168# @param domain The domain to operate on.
1169def balance_deep_and_shallow(domain):
1170    """Compute linear combination between stage as computed by
1171    gradient-limiters limiting using w, and stage computed by
1172    gradient-limiters limiting using h (h-limiter).
1173    The former takes precedence when heights are large compared to the
1174    bed slope while the latter takes precedence when heights are
1175    relatively small.  Anything in between is computed as a balanced
1176    linear combination in order to avoid numerical disturbances which
1177    would otherwise appear as a result of hard switching between
1178    modes.
1179
1180    Wrapper for C implementation
1181    """
1182
1183    from shallow_water_ext import balance_deep_and_shallow \
1184                                  as balance_deep_and_shallow_c
1185
1186    # Shortcuts
1187    wc = domain.quantities['stage'].centroid_values
1188    zc = domain.quantities['elevation'].centroid_values
1189    wv = domain.quantities['stage'].vertex_values
1190    zv = domain.quantities['elevation'].vertex_values
1191
1192    # Momentums at centroids
1193    xmomc = domain.quantities['xmomentum'].centroid_values
1194    ymomc = domain.quantities['ymomentum'].centroid_values
1195
1196    # Momentums at vertices
1197    xmomv = domain.quantities['xmomentum'].vertex_values
1198    ymomv = domain.quantities['ymomentum'].vertex_values
1199
1200    balance_deep_and_shallow_c(domain,
1201                               wc, zc, wv, zv, wc,
1202                               xmomc, ymomc, xmomv, ymomv)
1203
1204
1205################################################################################
1206# Boundary conditions - specific to the shallow water wave equation
1207################################################################################
1208
1209##
1210# @brief Class for a reflective boundary.
1211# @note Inherits from Boundary.
1212class Reflective_boundary(Boundary):
1213    """Reflective boundary returns same conserved quantities as
1214    those present in its neighbour volume but reflected.
1215
1216    This class is specific to the shallow water equation as it
1217    works with the momentum quantities assumed to be the second
1218    and third conserved quantities.
1219    """
1220
1221    ##
1222    # @brief Instantiate a Reflective_boundary.
1223    # @param domain
1224    def __init__(self, domain=None):
1225        Boundary.__init__(self)
1226
1227        if domain is None:
1228            msg = 'Domain must be specified for reflective boundary'
1229            raise Exception, msg
1230
1231        # Handy shorthands
1232        self.stage = domain.quantities['stage'].edge_values
1233        self.xmom = domain.quantities['xmomentum'].edge_values
1234        self.ymom = domain.quantities['ymomentum'].edge_values
1235        self.normals = domain.normals
1236
1237        self.conserved_quantities = num.zeros(3, num.float)
1238
1239    ##
1240    # @brief Return a representation of this instance.
1241    def __repr__(self):
1242        return 'Reflective_boundary'
1243
1244    ##
1245    # @brief Calculate reflections (reverse outward momentum).
1246    # @param vol_id
1247    # @param edge_id
1248    def evaluate(self, vol_id, edge_id):
1249        """Reflective boundaries reverses the outward momentum
1250        of the volume they serve.
1251        """
1252
1253        q = self.conserved_quantities
1254        q[0] = self.stage[vol_id, edge_id]
1255        q[1] = self.xmom[vol_id, edge_id]
1256        q[2] = self.ymom[vol_id, edge_id]
1257
1258        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
1259
1260        r = rotate(q, normal, direction = 1)
1261        r[1] = -r[1]
1262        q = rotate(r, normal, direction = -1)
1263
1264        return q
1265
1266
1267##
1268# @brief Class for a transmissive boundary.
1269# @note Inherits from Boundary.
1270class Transmissive_momentum_set_stage_boundary(Boundary):
1271    """Returns same momentum conserved quantities as
1272    those present in its neighbour volume.
1273    Sets stage by specifying a function f of time which may either be a
1274    vector function or a scalar function
1275
1276    Example:
1277
1278    def waveform(t):
1279        return sea_level + normalized_amplitude/cosh(t-25)**2
1280
1281    Bts = Transmissive_momentum_set_stage_boundary(domain, waveform)
1282
1283    Underlying domain must be specified when boundary is instantiated
1284    """
1285
1286    ##
1287    # @brief Instantiate a Reflective_boundary.
1288    # @param domain
1289    # @param function
1290    def __init__(self, domain=None, function=None):
1291        Boundary.__init__(self)
1292
1293        if domain is None:
1294            msg = 'Domain must be specified for this type boundary'
1295            raise Exception, msg
1296
1297        if function is None:
1298            msg = 'Function must be specified for this type boundary'
1299            raise Exception, msg
1300
1301        self.domain = domain
1302        self.function = function
1303
1304    ##
1305    # @brief Return a representation of this instance.
1306    def __repr__(self):
1307        return 'Transmissive_momentum_set_stage_boundary(%s)' %self.domain
1308
1309    ##
1310    # @brief Calculate transmissive results.
1311    # @param vol_id
1312    # @param edge_id
1313    def evaluate(self, vol_id, edge_id):
1314        """Transmissive momentum set stage boundaries return the edge momentum
1315        values of the volume they serve.
1316        """
1317
1318        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
1319
1320        t = self.domain.get_time()
1321
1322        if hasattr(self.function, 'time'):
1323            # Roll boundary over if time exceeds
1324            while t > self.function.time[-1]:
1325                msg = 'WARNING: domain time %.2f has exceeded' % t
1326                msg += 'time provided in '
1327                msg += 'transmissive_momentum_set_stage boundary object.\n'
1328                msg += 'I will continue, reusing the object from t==0'
1329                log.critical(msg)
1330                t -= self.function.time[-1]
1331
1332        value = self.function(t)
1333        try:
1334            x = float(value)
1335        except:
1336            x = float(value[0])
1337
1338        q[0] = x
1339        return q
1340
1341        # FIXME: Consider this (taken from File_boundary) to allow
1342        # spatial variation
1343        # if vol_id is not None and edge_id is not None:
1344        #     i = self.boundary_indices[ vol_id, edge_id ]
1345        #     return self.F(t, point_id = i)
1346        # else:
1347        #     return self.F(t)
1348
1349
1350##
1351# @brief Deprecated boundary class.
1352class Transmissive_Momentum_Set_Stage_boundary(Transmissive_momentum_set_stage_boundary):
1353    pass
1354
1355
1356##
1357# @brief A transmissive boundary, momentum set to zero.
1358# @note Inherits from Bouondary.
1359class Transmissive_stage_zero_momentum_boundary(Boundary):
1360    """Return same stage as those present in its neighbour volume.
1361    Set momentum to zero.
1362
1363    Underlying domain must be specified when boundary is instantiated
1364    """
1365
1366    ##
1367    # @brief Instantiate a Transmissive (zero momentum) boundary.
1368    # @param domain
1369    def __init__(self, domain=None):
1370        Boundary.__init__(self)
1371
1372        if domain is None:
1373            msg = ('Domain must be specified for '
1374                   'Transmissive_stage_zero_momentum boundary')
1375            raise Exception, msg
1376
1377        self.domain = domain
1378
1379    ##
1380    # @brief Return a representation of this instance.
1381    def __repr__(self):
1382        return 'Transmissive_stage_zero_momentum_boundary(%s)' % self.domain
1383
1384    ##
1385    # @brief Calculate transmissive (zero momentum) results.
1386    # @param vol_id
1387    # @param edge_id
1388    def evaluate(self, vol_id, edge_id):
1389        """Transmissive boundaries return the edge values
1390        of the volume they serve.
1391        """
1392
1393        q = self.domain.get_conserved_quantities(vol_id, edge=edge_id)
1394
1395        q[1] = q[2] = 0.0
1396        return q
1397
1398
1399##
1400# @brief Class for a Dirichlet discharge boundary.
1401# @note Inherits from Boundary.
1402class Dirichlet_discharge_boundary(Boundary):
1403    """
1404    Sets stage (stage0)
1405    Sets momentum (wh0) in the inward normal direction.
1406
1407    Underlying domain must be specified when boundary is instantiated
1408    """
1409
1410    ##
1411    # @brief Instantiate a Dirichlet discharge boundary.
1412    # @param domain
1413    # @param stage0
1414    # @param wh0
1415    def __init__(self, domain=None, stage0=None, wh0=None):
1416        Boundary.__init__(self)
1417
1418        if domain is None:
1419            msg = 'Domain must be specified for this type of boundary'
1420            raise Exception, msg
1421
1422        if stage0 is None:
1423            raise Exception, 'Stage must be specified for this type of boundary'
1424
1425        if wh0 is None:
1426            wh0 = 0.0
1427
1428        self.domain = domain
1429        self.stage0 = stage0
1430        self.wh0 = wh0
1431
1432    ##
1433    # @brief Return a representation of this instance.
1434    def __repr__(self):
1435        return 'Dirichlet_Discharge_boundary(%s)' % self.domain
1436
1437    ##
1438    # @brief Calculate Dirichlet discharge boundary results.
1439    # @param vol_id
1440    # @param edge_id
1441    def evaluate(self, vol_id, edge_id):
1442        """Set discharge in the (inward) normal direction"""
1443
1444        normal = self.domain.get_normal(vol_id,edge_id)
1445        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1446        return q
1447
1448        # FIXME: Consider this (taken from File_boundary) to allow
1449        # spatial variation
1450        # if vol_id is not None and edge_id is not None:
1451        #     i = self.boundary_indices[ vol_id, edge_id ]
1452        #     return self.F(t, point_id = i)
1453        # else:
1454        #     return self.F(t)
1455
1456
1457# Backward compatibility
1458# FIXME(Ole): Deprecate
1459##
1460# @brief Deprecated
1461class Dirichlet_Discharge_boundary(Dirichlet_discharge_boundary):
1462    pass
1463
1464
1465class Inflow_boundary(Boundary):
1466    """Apply given flow in m^3/s to boundary segment.
1467    Depth and momentum is derived using Manning's formula.
1468
1469    Underlying domain must be specified when boundary is instantiated
1470    """
1471   
1472    # FIXME (Ole): This is work in progress and definitely not finished.
1473    # The associated test has been disabled
1474
1475    def __init__(self, domain=None, rate=0.0):
1476        Boundary.__init__(self)
1477
1478        if domain is None:
1479            msg = 'Domain must be specified for '
1480            msg += 'Inflow boundary'
1481            raise Exception, msg
1482
1483        self.domain = domain
1484       
1485        # FIXME(Ole): Allow rate to be time dependent as well
1486        self.rate = rate
1487        self.tag = None # Placeholder for tag associated with this object.
1488
1489    def __repr__(self):
1490        return 'Inflow_boundary(%s)' %self.domain
1491
1492    def evaluate(self, vol_id, edge_id):
1493        """Apply inflow rate at each edge of this boundary
1494        """
1495       
1496        # First find all segments having the same tag is vol_id, edge_id
1497        # This will be done the first time evaluate is called.
1498        if self.tag is None:
1499            boundary = self.domain.boundary
1500            self.tag = boundary[(vol_id, edge_id)]       
1501           
1502            # Find total length of boundary with this tag
1503            length = 0.0
1504            for v_id, e_id in boundary:
1505                if self.tag == boundary[(v_id, e_id)]:
1506                    length += self.domain.mesh.get_edgelength(v_id, e_id)           
1507
1508            self.length = length
1509            self.average_momentum = self.rate/length
1510           
1511           
1512        # Average momentum has now been established across this boundary
1513        # Compute momentum in the inward normal direction
1514       
1515        inward_normal = -self.domain.mesh.get_normal(vol_id, edge_id)       
1516        xmomentum, ymomentum = self.average_momentum * inward_normal
1517           
1518        # Compute depth based on Manning's formula v = 1/n h^{2/3} sqrt(S)
1519        # Where v is velocity, n is manning's coefficient, h is depth and S is the slope into the domain.
1520        # Let mu be the momentum (vh), then this equation becomes: mu = 1/n h^{5/3} sqrt(S)
1521        # from which we can isolate depth to get
1522        # h = (mu n/sqrt(S) )^{3/5}
1523       
1524        slope = 0 # get gradient for this triangle dot normal
1525       
1526        # get manning coef from this triangle
1527        friction = self.domain.get_quantity('friction').get_values(location='edges', 
1528                                                                   indices=[vol_id])[0]
1529        mannings_n = friction[edge_id]
1530
1531        if slope > epsilon and mannings_n > epsilon:
1532            depth = pow(self.average_momentum * mannings_n/math.sqrt(slope), 3.0/5) 
1533        else:
1534            depth = 1.0
1535           
1536        # Elevation on this edge   
1537       
1538        z = self.domain.get_quantity('elevation').get_values(location='edges', 
1539                                                             indices=[vol_id])[0]
1540        elevation = z[edge_id]
1541           
1542        # Assign conserved quantities and return
1543        q = num.array([elevation + depth, xmomentum, ymomentum], num.Float)
1544        return q
1545
1546
1547       
1548   
1549           
1550       
1551class Field_boundary(Boundary):
1552    """Set boundary from given field represented in an sww file containing
1553    values for stage, xmomentum and ymomentum.
1554
1555    Optionally, the user can specify mean_stage to offset the stage provided
1556    in the sww file.
1557
1558    This function is a thin wrapper around the generic File_boundary. The
1559    difference between the file_boundary and field_boundary is only that the
1560    field_boundary will allow you to change the level of the stage height when
1561    you read in the boundary condition. This is very useful when running
1562    different tide heights in the same area as you need only to convert one
1563    boundary condition to a SWW file, ideally for tide height of 0 m
1564    (saving disk space). Then you can use field_boundary to read this SWW file
1565    and change the stage height (tide) on the fly depending on the scenario.
1566    """
1567
1568    ##
1569    # @brief Construct an instance of a 'field' boundary.
1570    # @param filename Name of SWW file containing stage, x and ymomentum.
1571    # @param domain Shallow water domain for which the boundary applies.
1572    # @param mean_stage Mean water level added to stage derived from SWW file.
1573    # @param time_thinning Time step thinning factor.
1574    # @param time_limit
1575    # @param boundary_polygon
1576    # @param default_boundary None or an instance of Boundary.
1577    # @param use_cache True if caching is to be used.
1578    # @param verbose True if this method is to be verbose.
1579    def __init__(self,
1580                 filename,
1581                 domain,
1582                 mean_stage=0.0,
1583                 time_thinning=1,
1584                 time_limit=None,
1585                 boundary_polygon=None,
1586                 default_boundary=None,
1587                 use_cache=False,
1588                 verbose=False):
1589        """Constructor
1590
1591        filename: Name of sww file
1592        domain: pointer to shallow water domain for which the boundary applies
1593        mean_stage: The mean water level which will be added to stage derived
1594                    from the boundary condition
1595        time_thinning: Will set how many time steps from the sww file read in
1596                       will be interpolated to the boundary. For example if
1597                       the sww file has 1 second time steps and is 24 hours
1598                       in length it has 86400 time steps. If you set
1599                       time_thinning to 1 it will read all these steps.
1600                       If you set it to 100 it will read every 100th step eg
1601                       only 864 step. This parameter is very useful to increase
1602                       the speed of a model run that you are setting up
1603                       and testing.
1604
1605        default_boundary: Must be either None or an instance of a
1606                          class descending from class Boundary.
1607                          This will be used in case model time exceeds
1608                          that available in the underlying data.
1609
1610                          Note that mean_stage will also be added to this.
1611                                               
1612        use_cache:
1613        verbose:
1614        """
1615
1616        # Create generic file_boundary object
1617        self.file_boundary = File_boundary(filename,
1618                                           domain,
1619                                           time_thinning=time_thinning,
1620                                           time_limit=time_limit,
1621                                           boundary_polygon=boundary_polygon,
1622                                           default_boundary=default_boundary,
1623                                           use_cache=use_cache,
1624                                           verbose=verbose)
1625
1626        # Record information from File_boundary
1627        self.F = self.file_boundary.F
1628        self.domain = self.file_boundary.domain
1629
1630        # Record mean stage
1631        self.mean_stage = mean_stage
1632
1633    ##
1634    # @note Generate a string representation of this instance.
1635    def __repr__(self):
1636        return 'Field boundary'
1637
1638    ##
1639    # @brief Calculate 'field' boundary results.
1640    # @param vol_id
1641    # @param edge_id
1642    def evaluate(self, vol_id=None, edge_id=None):
1643        """Return linearly interpolated values based on domain.time
1644
1645        vol_id and edge_id are ignored
1646        """
1647
1648        # Evaluate file boundary
1649        q = self.file_boundary.evaluate(vol_id, edge_id)
1650
1651        # Adjust stage
1652        for j, name in enumerate(self.domain.conserved_quantities):
1653            if name == 'stage':
1654                q[j] += self.mean_stage
1655        return q
1656
1657
1658################################################################################
1659# Standard forcing terms
1660################################################################################
1661
1662##
1663# @brief Apply gravitational pull in the presence of bed slope.
1664# @param domain The domain to apply gravity to.
1665# @note Wrapper for C function gravity_c().
1666def gravity(domain):
1667    """Apply gravitational pull in the presence of bed slope
1668    Wrapper calls underlying C implementation
1669    """
1670
1671    from shallow_water_ext import gravity as gravity_c
1672
1673    xmom = domain.quantities['xmomentum'].explicit_update
1674    ymom = domain.quantities['ymomentum'].explicit_update
1675
1676    stage = domain.quantities['stage']
1677    elevation = domain.quantities['elevation']
1678
1679    h = stage.centroid_values - elevation.centroid_values
1680    z = elevation.vertex_values
1681
1682    x = domain.get_vertex_coordinates()
1683    g = domain.g
1684
1685    gravity_c(g, h, z, x, xmom, ymom)    #, 1.0e-6)
1686
1687##
1688# @brief Apply friction to a surface (implicit).
1689# @param domain The domain to apply Manning friction to.
1690# @note Wrapper for C function manning_friction_c().
1691def manning_friction_implicit(domain):
1692    """Apply (Manning) friction to water momentum
1693    Wrapper for c version
1694    """
1695
1696    from shallow_water_ext import manning_friction as manning_friction_c
1697
1698    xmom = domain.quantities['xmomentum']
1699    ymom = domain.quantities['ymomentum']
1700
1701    w = domain.quantities['stage'].centroid_values
1702    z = domain.quantities['elevation'].centroid_values
1703
1704    uh = xmom.centroid_values
1705    vh = ymom.centroid_values
1706    eta = domain.quantities['friction'].centroid_values
1707
1708    xmom_update = xmom.semi_implicit_update
1709    ymom_update = ymom.semi_implicit_update
1710
1711    N = len(domain)
1712    eps = domain.minimum_allowed_height
1713    g = domain.g
1714
1715    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1716
1717##
1718# @brief Apply friction to a surface (explicit).
1719# @param domain The domain to apply Manning friction to.
1720# @note Wrapper for C function manning_friction_c().
1721def manning_friction_explicit(domain):
1722    """Apply (Manning) friction to water momentum
1723    Wrapper for c version
1724    """
1725
1726    from shallow_water_ext import manning_friction as manning_friction_c
1727
1728    xmom = domain.quantities['xmomentum']
1729    ymom = domain.quantities['ymomentum']
1730
1731    w = domain.quantities['stage'].centroid_values
1732    z = domain.quantities['elevation'].centroid_values
1733
1734    uh = xmom.centroid_values
1735    vh = ymom.centroid_values
1736    eta = domain.quantities['friction'].centroid_values
1737
1738    xmom_update = xmom.explicit_update
1739    ymom_update = ymom.explicit_update
1740
1741    N = len(domain)
1742    eps = domain.minimum_allowed_height
1743    g = domain.g
1744
1745    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1746
1747
1748# FIXME (Ole): This was implemented for use with one of the analytical solutions (Sampson?)
1749##
1750# @brief Apply linear friction to a surface.
1751# @param domain The domain to apply Manning friction to.
1752# @note Is this still used (30 Oct 2007)?
1753def linear_friction(domain):
1754    """Apply linear friction to water momentum
1755
1756    Assumes quantity: 'linear_friction' to be present
1757    """
1758
1759    from math import sqrt
1760
1761    w = domain.quantities['stage'].centroid_values
1762    z = domain.quantities['elevation'].centroid_values
1763    h = w-z
1764
1765    uh = domain.quantities['xmomentum'].centroid_values
1766    vh = domain.quantities['ymomentum'].centroid_values
1767    tau = domain.quantities['linear_friction'].centroid_values
1768
1769    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1770    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1771
1772    N = len(domain) # number_of_triangles
1773    eps = domain.minimum_allowed_height
1774    g = domain.g #Not necessary? Why was this added?
1775
1776    for k in range(N):
1777        if tau[k] >= eps:
1778            if h[k] >= eps:
1779                S = -tau[k]/h[k]
1780
1781                #Update momentum
1782                xmom_update[k] += S*uh[k]
1783                ymom_update[k] += S*vh[k]
1784
1785def depth_dependent_friction(domain, default_friction,
1786                             surface_roughness_data,
1787                             verbose=False):
1788    """Returns an array of friction values for each wet element adjusted for depth.
1789
1790    Inputs:
1791        domain - computational domain object
1792        default_friction - depth independent bottom friction
1793        surface_roughness_data - N x 5 array of n0, d1, n1, d2, n2 values for each
1794        friction region.
1795
1796    Outputs:
1797        wet_friction - Array that can be used directly to update friction as follows:
1798                       domain.set_quantity('friction', wet_friction)
1799
1800       
1801       
1802    """
1803
1804    import numpy as num
1805   
1806    # Create a temp array to store updated depth dependent friction for wet elements
1807    # EHR this is outwardly inneficient but not obvious how to avoid recreating each call??????
1808    N=len(domain)
1809    wet_friction    = num.zeros(N, num.float)
1810    wet_friction[:] = default_n0   # Initially assign default_n0 to all array so sure have no zeros values
1811   
1812   
1813    depth = domain.create_quantity_from_expression('stage - elevation')  # create depth instance for this timestep
1814    # Recompute depth as vector 
1815    d = depth.get_values(location='centroids')
1816 
1817    # rebuild the 'friction' values adjusted for depth at this instant
1818    for i in domain.get_wet_elements():                                  # loop for each wet element in domain
1819       
1820        # Get roughness data for each element
1821        n0 = float(surface_roughness_data[i,0])
1822        d1 = float(surface_roughness_data[i,1])
1823        n1 = float(surface_roughness_data[i,2])
1824        d2 = float(surface_roughness_data[i,3])
1825        n2 = float(surface_roughness_data[i,4])
1826       
1827       
1828        # Recompute friction values from depth for this element
1829               
1830        if d[i]   <= d1: 
1831            depth_dependent_friction = n1
1832        elif d[i] >= d2:
1833            depth_dependent_friction = n2
1834        else:
1835            depth_dependent_friction = n1+((n2-n1)/(d2-d1))*(d[i]-d1)
1836           
1837        # check sanity of result
1838        if (depth_dependent_friction  < 0.010 or depth_dependent_friction > 9999.0) :
1839            log.critical('%s >>>> WARNING: computed depth_dependent friction '
1840                         'out of range, ddf%f, n1=%f, n2=%f'
1841                         % (model_data.basename,
1842                            depth_dependent_friction, n1, n2))
1843       
1844        # update depth dependent friction  for that wet element
1845        wet_friction[i] = depth_dependent_friction
1846       
1847    # EHR add code to show range of 'friction across domain at this instant as sanity check?????????
1848   
1849    if verbose :
1850        nvals=domain.get_quantity('friction').get_values(location='centroids')        # return array of domain nvals
1851        n_min=min(nvals)
1852        n_max=max(nvals)
1853       
1854        log.critical('         ++++ calculate_depth_dependent_friction - '
1855                     'Updated friction - range  %7.3f to %7.3f'
1856                     % (n_min, n_max))
1857   
1858    return wet_friction
1859
1860
1861################################################################################
1862# Experimental auxiliary functions
1863################################################################################
1864
1865##
1866# @brief Check forcefield parameter.
1867# @param f Object to check.
1868# @note 'f' may be a callable object or a scalar value.
1869def check_forcefield(f):
1870    """Check that force object is as expected.
1871   
1872    Check that f is either:
1873    1: a callable object f(t,x,y), where x and y are vectors
1874       and that it returns an array or a list of same length
1875       as x and y
1876    2: a scalar
1877    """
1878
1879    if callable(f):
1880        N = 3
1881        x = num.ones(3, num.float)
1882        y = num.ones(3, num.float)
1883        try:
1884            q = f(1.0, x=x, y=y)
1885        except Exception, e:
1886            msg = 'Function %s could not be executed:\n%s' %(f, e)
1887            # FIXME: Reconsider this semantics
1888            raise Exception, msg
1889
1890        try:
1891            q = num.array(q, num.float)
1892        except:
1893            msg = ('Return value from vector function %s could not '
1894                   'be converted into a numeric array of floats.\nSpecified '
1895                   'function should return either list or array.' % f)
1896            raise Exception, msg
1897
1898        # Is this really what we want?
1899        # info is "(func name, filename, defining line)"
1900        func_info = (f.func_name, f.func_code.co_filename,
1901                     f.func_code.co_firstlineno)
1902        func_msg = 'Function %s (defined in %s, line %d)' % func_info
1903        try:
1904            result_len = len(q)
1905        except:
1906            msg = '%s must return vector' % func_msg
1907            self.fail(msg)
1908        msg = '%s must return vector of length %d' % (func_msg, N)
1909        assert result_len == N, msg
1910    else:
1911        try:
1912            f = float(f)
1913        except:
1914            msg = ('Force field %s must be a scalar value coercible to float.'
1915                   % str(f))
1916            raise Exception, msg
1917
1918    return f
1919
1920
1921##
1922# Class to apply a wind stress to a domain.
1923class Wind_stress:
1924    """Apply wind stress to water momentum in terms of
1925    wind speed [m/s] and wind direction [degrees]
1926    """
1927
1928    ##
1929    # @brief Create an instance of Wind_stress.
1930    # @param *args
1931    # @param **kwargs
1932    def __init__(self, *args, **kwargs):
1933        """Initialise windfield from wind speed s [m/s]
1934        and wind direction phi [degrees]
1935
1936        Inputs v and phi can be either scalars or Python functions, e.g.
1937
1938        W = Wind_stress(10, 178)
1939
1940        #FIXME - 'normal' degrees are assumed for now, i.e. the
1941        vector (1,0) has zero degrees.
1942        We may need to convert from 'compass' degrees later on and also
1943        map from True north to grid north.
1944
1945        Arguments can also be Python functions of t,x,y as in
1946
1947        def speed(t,x,y):
1948            ...
1949            return s
1950
1951        def angle(t,x,y):
1952            ...
1953            return phi
1954
1955        where x and y are vectors.
1956
1957        and then pass the functions in
1958
1959        W = Wind_stress(speed, angle)
1960
1961        The instantiated object W can be appended to the list of
1962        forcing_terms as in
1963
1964        Alternatively, one vector valued function for (speed, angle)
1965        can be applied, providing both quantities simultaneously.
1966        As in
1967        W = Wind_stress(F), where returns (speed, angle) for each t.
1968
1969        domain.forcing_terms.append(W)
1970        """
1971
1972        from anuga.config import rho_a, rho_w, eta_w
1973
1974        if len(args) == 2:
1975            s = args[0]
1976            phi = args[1]
1977        elif len(args) == 1:
1978            # Assume vector function returning (s, phi)(t,x,y)
1979            vector_function = args[0]
1980            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1981            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1982        else:
1983           # Assume info is in 2 keyword arguments
1984           if len(kwargs) == 2:
1985               s = kwargs['s']
1986               phi = kwargs['phi']
1987           else:
1988               raise Exception, 'Assumes two keyword arguments: s=..., phi=....'
1989
1990        self.speed = check_forcefield(s)
1991        self.phi = check_forcefield(phi)
1992
1993        self.const = eta_w*rho_a/rho_w
1994
1995    ##
1996    # @brief 'execute' this class instance.
1997    # @param domain
1998    def __call__(self, domain):
1999        """Evaluate windfield based on values found in domain"""
2000
2001        from math import pi, cos, sin, sqrt
2002
2003        xmom_update = domain.quantities['xmomentum'].explicit_update
2004        ymom_update = domain.quantities['ymomentum'].explicit_update
2005
2006        N = len(domain)    # number_of_triangles
2007        t = domain.time
2008
2009        if callable(self.speed):
2010            xc = domain.get_centroid_coordinates()
2011            s_vec = self.speed(t, xc[:,0], xc[:,1])
2012        else:
2013            # Assume s is a scalar
2014            try:
2015                s_vec = self.speed * num.ones(N, num.float)
2016            except:
2017                msg = 'Speed must be either callable or a scalar: %s' %self.s
2018                raise msg
2019
2020        if callable(self.phi):
2021            xc = domain.get_centroid_coordinates()
2022            phi_vec = self.phi(t, xc[:,0], xc[:,1])
2023        else:
2024            # Assume phi is a scalar
2025
2026            try:
2027                phi_vec = self.phi * num.ones(N, num.float)
2028            except:
2029                msg = 'Angle must be either callable or a scalar: %s' %self.phi
2030                raise msg
2031
2032        assign_windfield_values(xmom_update, ymom_update,
2033                                s_vec, phi_vec, self.const)
2034
2035
2036##
2037# @brief Assign wind field values
2038# @param xmom_update
2039# @param ymom_update
2040# @param s_vec
2041# @param phi_vec
2042# @param const
2043def assign_windfield_values(xmom_update, ymom_update,
2044                            s_vec, phi_vec, const):
2045    """Python version of assigning wind field to update vectors.
2046    A C version also exists (for speed)
2047    """
2048
2049    from math import pi, cos, sin, sqrt
2050
2051    N = len(s_vec)
2052    for k in range(N):
2053        s = s_vec[k]
2054        phi = phi_vec[k]
2055
2056        # Convert to radians
2057        phi = phi*pi/180
2058
2059        # Compute velocity vector (u, v)
2060        u = s*cos(phi)
2061        v = s*sin(phi)
2062
2063        # Compute wind stress
2064        S = const * sqrt(u**2 + v**2)
2065        xmom_update[k] += S*u
2066        ymom_update[k] += S*v
2067
2068
2069##
2070# @brief A class for a general explicit forcing term.
2071class General_forcing:
2072    """General explicit forcing term for update of quantity
2073
2074    This is used by Inflow and Rainfall for instance
2075
2076
2077    General_forcing(quantity_name, rate, center, radius, polygon)
2078
2079    domain:     ANUGA computational domain
2080    quantity_name: Name of quantity to update.
2081                   It must be a known conserved quantity.
2082
2083    rate [?/s]: Total rate of change over the specified area.
2084                This parameter can be either a constant or a
2085                function of time. Positive values indicate increases,
2086                negative values indicate decreases.
2087                Rate can be None at initialisation but must be specified
2088                before forcing term is applied (i.e. simulation has started).
2089
2090    center [m]: Coordinates at center of flow point
2091    radius [m]: Size of circular area
2092    polygon:    Arbitrary polygon
2093    default_rate: Rate to be used if rate fails (e.g. if model time exceeds its data)
2094                  Admissible types: None, constant number or function of t
2095
2096
2097    Either center, radius or polygon can be specified but not both.
2098    If neither are specified the entire domain gets updated.
2099    All coordinates to be specified in absolute UTM coordinates (x, y) assuming the zone of domain.
2100
2101    Inflow or Rainfall for examples of use
2102    """
2103
2104
2105    # FIXME (AnyOne) : Add various methods to allow spatial variations
2106
2107    ##
2108    # @brief Create an instance of this forcing term.
2109    # @param domain
2110    # @param quantity_name
2111    # @param rate
2112    # @param center
2113    # @param radius
2114    # @param polygon
2115    # @param default_rate
2116    # @param verbose
2117    def __init__(self,
2118                 domain,
2119                 quantity_name,
2120                 rate=0.0,
2121                 center=None,
2122                 radius=None,
2123                 polygon=None,
2124                 default_rate=None,
2125                 verbose=False):
2126
2127        from math import pi, cos, sin
2128
2129        if center is None:
2130            msg = 'I got radius but no center.'
2131            assert radius is None, msg
2132
2133        if radius is None:
2134            msg += 'I got center but no radius.'
2135            assert center is None, msg
2136
2137        self.domain = domain
2138        self.quantity_name = quantity_name
2139        self.rate = rate
2140        self.center = ensure_numeric(center)
2141        self.radius = radius
2142        self.polygon = polygon
2143        self.verbose = verbose
2144        self.value = 0.0    # Can be used to remember value at
2145                            # previous timestep in order to obtain rate
2146
2147        # Get boundary (in absolute coordinates)
2148        bounding_polygon = domain.get_boundary_polygon()
2149
2150        # Update area if applicable
2151        if center is not None and radius is not None:
2152            assert len(center) == 2
2153            msg = 'Polygon cannot be specified when center and radius are'
2154            assert polygon is None, msg
2155
2156            # Check that circle center lies within the mesh.
2157            msg = 'Center %s specified for forcing term did not' % str(center)
2158            msg += 'fall within the domain boundary.'
2159            assert is_inside_polygon(center, bounding_polygon), msg
2160
2161            # Check that circle periphery lies within the mesh.
2162            N = 100
2163            periphery_points = []
2164            for i in range(N):
2165                theta = 2*pi*i/100
2166
2167                x = center[0] + radius*cos(theta)
2168                y = center[1] + radius*sin(theta)
2169
2170                periphery_points.append([x,y])
2171
2172            for point in periphery_points:
2173                msg = 'Point %s on periphery for forcing term' % str(point)
2174                msg += ' did not fall within the domain boundary.'
2175                assert is_inside_polygon(point, bounding_polygon), msg
2176
2177        if polygon is not None:
2178            # Check that polygon lies within the mesh.
2179            for point in self.polygon:
2180                msg = 'Point %s in polygon for forcing term' % str(point)
2181                msg += ' did not fall within the domain boundary.'
2182                assert is_inside_polygon(point, bounding_polygon), msg
2183
2184        # Pointer to update vector
2185        self.update = domain.quantities[self.quantity_name].explicit_update
2186
2187        # Determine indices in flow area
2188        N = len(domain)
2189        points = domain.get_centroid_coordinates(absolute=True)
2190
2191        # Calculate indices in exchange area for this forcing term
2192        self.exchange_indices = None
2193        if self.center is not None and self.radius is not None:
2194            # Inlet is circular
2195            inlet_region = 'center=%s, radius=%s' % (self.center, self.radius)
2196
2197            self.exchange_indices = []
2198            for k in range(N):
2199                x, y = points[k,:]    # Centroid
2200
2201                c = self.center
2202                if ((x-c[0])**2+(y-c[1])**2) < self.radius**2:
2203                    self.exchange_indices.append(k)
2204
2205        if self.polygon is not None:
2206            # Inlet is polygon
2207            inlet_region = 'polygon=%s' % (self.polygon) 
2208            self.exchange_indices = inside_polygon(points, self.polygon)
2209
2210        if self.exchange_indices is None:
2211            self.exchange_area = polygon_area(bounding_polygon)
2212        else:   
2213            if len(self.exchange_indices) == 0:
2214                msg = 'No triangles have been identified in '
2215                msg += 'specified region: %s' % inlet_region
2216                raise Exception, msg
2217
2218            # Compute exchange area as the sum of areas of triangles identified
2219            # by circle or polygon
2220            self.exchange_area = 0.0
2221            for i in self.exchange_indices:
2222                self.exchange_area += domain.areas[i]
2223           
2224
2225        msg = 'Exchange area in forcing term'
2226        msg += ' has area = %f' %self.exchange_area
2227        assert self.exchange_area > 0.0           
2228           
2229               
2230
2231           
2232        # Check and store default_rate
2233        msg = ('Keyword argument default_rate must be either None '
2234               'or a function of time.\nI got %s.' % str(default_rate))
2235        assert (default_rate is None or
2236                type(default_rate) in [IntType, FloatType] or
2237                callable(default_rate)), msg
2238
2239        if default_rate is not None:
2240            # If it is a constant, make it a function
2241            if not callable(default_rate):
2242                tmp = default_rate
2243                default_rate = lambda t: tmp
2244
2245            # Check that default_rate is a function of one argument
2246            try:
2247                default_rate(0.0)
2248            except:
2249                raise Exception, msg
2250
2251        self.default_rate = default_rate
2252        self.default_rate_invoked = False    # Flag
2253
2254    ##
2255    # @brief Execute this instance.
2256    # @param domain
2257    def __call__(self, domain):
2258        """Apply inflow function at time specified in domain, update stage"""
2259
2260        # Call virtual method allowing local modifications
2261        t = domain.get_time()
2262        try:
2263            rate = self.update_rate(t)
2264        except Modeltime_too_early, e:
2265            raise Modeltime_too_early, e
2266        except Modeltime_too_late, e:
2267            if self.default_rate is None:
2268                raise Exception, e    # Reraise exception
2269            else:
2270                # Pass control to default rate function
2271                rate = self.default_rate(t)
2272
2273                if self.default_rate_invoked is False:
2274                    # Issue warning the first time
2275                    msg = ('%s\n'
2276                           'Instead I will use the default rate: %s\n'
2277                           'Note: Further warnings will be supressed'
2278                           % (str(e), str(self.default_rate)))
2279                    warn(msg)
2280
2281                    # FIXME (Ole): Replace this crude flag with
2282                    # Python's ability to print warnings only once.
2283                    # See http://docs.python.org/lib/warning-filter.html
2284                    self.default_rate_invoked = True
2285
2286        if rate is None:
2287            msg = ('Attribute rate must be specified in General_forcing '
2288                   'or its descendants before attempting to call it')
2289            raise Exception, msg
2290
2291        # Now rate is a number
2292        if self.verbose is True:
2293            log.critical('Rate of %s at time = %.2f = %f'
2294                         % (self.quantity_name, domain.get_time(), rate))
2295
2296        if self.exchange_indices is None:
2297            self.update[:] += rate
2298        else:
2299            # Brute force assignment of restricted rate
2300            for k in self.exchange_indices:
2301                self.update[k] += rate
2302
2303    ##
2304    # @brief Update the internal rate.
2305    # @param t A callable or scalar used to set the rate.
2306    # @return The new rate.
2307    def update_rate(self, t):
2308        """Virtual method allowing local modifications by writing an
2309        overriding version in descendant
2310        """
2311
2312        if callable(self.rate):
2313            rate = self.rate(t)
2314        else:
2315            rate = self.rate
2316
2317        return rate
2318
2319    ##
2320    # @brief Get values for the specified quantity.
2321    # @param quantity_name Name of the quantity of interest.
2322    # @return The value(s) of the quantity.
2323    # @note If 'quantity_name' is None, use self.quantity_name.
2324    def get_quantity_values(self, quantity_name=None):
2325        """Return values for specified quantity restricted to opening
2326
2327        Optionally a quantity name can be specified if values from another
2328        quantity is sought
2329        """
2330
2331        if quantity_name is None:
2332            quantity_name = self.quantity_name
2333
2334        q = self.domain.quantities[quantity_name]
2335        return q.get_values(location='centroids',
2336                            indices=self.exchange_indices)
2337
2338    ##
2339    # @brief Set value for the specified quantity.
2340    # @param val The value object used to set value.
2341    # @param quantity_name Name of the quantity of interest.
2342    # @note If 'quantity_name' is None, use self.quantity_name.
2343    def set_quantity_values(self, val, quantity_name=None):
2344        """Set values for specified quantity restricted to opening
2345
2346        Optionally a quantity name can be specified if values from another
2347        quantity is sought
2348        """
2349
2350        if quantity_name is None:
2351            quantity_name = self.quantity_name
2352
2353        q = self.domain.quantities[self.quantity_name]
2354        q.set_values(val,
2355                     location='centroids',
2356                     indices=self.exchange_indices)
2357
2358
2359##
2360# @brief A class for rainfall forcing function.
2361# @note Inherits from General_forcing.
2362class Rainfall(General_forcing):
2363    """Class Rainfall - general 'rain over entire domain' forcing term.
2364
2365    Used for implementing Rainfall over the entire domain.
2366
2367        Current Limited to only One Gauge..
2368
2369        Need to add Spatial Varying Capability
2370        (This module came from copying and amending the Inflow Code)
2371
2372    Rainfall(rain)
2373
2374    domain
2375    rain [mm/s]:  Total rain rate over the specified domain.
2376                  NOTE: Raingauge Data needs to reflect the time step.
2377                  IE: if Gauge is mm read at a time step, then the input
2378                  here is as mm/(timeStep) so 10mm in 5minutes becomes
2379                  10/(5x60) = 0.0333mm/s.
2380
2381                  This parameter can be either a constant or a
2382                  function of time. Positive values indicate inflow,
2383                  negative values indicate outflow.
2384                  (and be used for Infiltration - Write Seperate Module)
2385                  The specified flow will be divided by the area of
2386                  the inflow region and then applied to update the
2387                  stage quantity.
2388
2389    polygon: Specifies a polygon to restrict the rainfall.
2390
2391    Examples
2392    How to put them in a run File...
2393
2394    #------------------------------------------------------------------------
2395    # Setup specialised forcing terms
2396    #------------------------------------------------------------------------
2397    # This is the new element implemented by Ole and Rudy to allow direct
2398    # input of Rainfall in mm/s
2399
2400    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms'))
2401                        # Note need path to File in String.
2402                        # Else assumed in same directory
2403
2404    domain.forcing_terms.append(catchmentrainfall)
2405    """
2406
2407    ##
2408    # @brief Create an instance of the class.
2409    # @param domain Domain of interest.
2410    # @param rate Total rain rate over the specified domain (mm/s).
2411    # @param center
2412    # @param radius
2413    # @param polygon Polygon  to restrict rainfall.
2414    # @param default_rate
2415    # @param verbose True if this instance is to be verbose.
2416    def __init__(self,
2417                 domain,
2418                 rate=0.0,
2419                 center=None,
2420                 radius=None,
2421                 polygon=None,
2422                 default_rate=None,
2423                 verbose=False):
2424
2425        # Converting mm/s to m/s to apply in ANUGA)
2426        if callable(rate):
2427            rain = lambda t: rate(t)/1000.0
2428        else:
2429            rain = rate/1000.0
2430
2431        if default_rate is not None:
2432            if callable(default_rate):
2433                default_rain = lambda t: default_rate(t)/1000.0
2434            else:
2435                default_rain = default_rate/1000.0
2436        else:
2437            default_rain = None
2438
2439           
2440           
2441        General_forcing.__init__(self,
2442                                 domain,
2443                                 'stage',
2444                                 rate=rain,
2445                                 center=center,
2446                                 radius=radius,
2447                                 polygon=polygon,
2448                                 default_rate=default_rain,
2449                                 verbose=verbose)
2450
2451
2452##
2453# @brief A class for inflow (rain and drain) forcing function.
2454# @note Inherits from General_forcing.
2455class Inflow(General_forcing):
2456    """Class Inflow - general 'rain and drain' forcing term.
2457
2458    Useful for implementing flows in and out of the domain.
2459
2460    Inflow(flow, center, radius, polygon)
2461
2462    domain
2463    rate [m^3/s]: Total flow rate over the specified area.
2464                  This parameter can be either a constant or a
2465                  function of time. Positive values indicate inflow,
2466                  negative values indicate outflow.
2467                  The specified flow will be divided by the area of
2468                  the inflow region and then applied to update stage.
2469    center [m]: Coordinates at center of flow point
2470    radius [m]: Size of circular area
2471    polygon:    Arbitrary polygon.
2472
2473    Either center, radius or polygon must be specified
2474
2475    Examples
2476
2477    # Constant drain at 0.003 m^3/s.
2478    # The outflow area is 0.07**2*pi=0.0154 m^2
2479    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
2480    #
2481    Inflow((0.7, 0.4), 0.07, -0.003)
2482
2483
2484    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
2485    # The inflow area is 0.03**2*pi = 0.00283 m^2
2486    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s
2487    # over the specified area
2488    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
2489
2490
2491    #------------------------------------------------------------------------
2492    # Setup specialised forcing terms
2493    #------------------------------------------------------------------------
2494    # This is the new element implemented by Ole to allow direct input
2495    # of Inflow in m^3/s
2496
2497    hydrograph = Inflow(center=(320, 300), radius=10,
2498                        rate=file_function('Q/QPMF_Rot_Sub13.tms'))
2499
2500    domain.forcing_terms.append(hydrograph)
2501    """
2502
2503    ##
2504    # @brief Create an instance of the class.
2505    # @param domain Domain of interest.
2506    # @param rate Total rain rate over the specified domain (mm/s).
2507    # @param center
2508    # @param radius
2509    # @param polygon Polygon  to restrict rainfall.
2510    # @param default_rate
2511    # @param verbose True if this instance is to be verbose.
2512    def __init__(self,
2513                 domain,
2514                 rate=0.0,
2515                 center=None,
2516                 radius=None,
2517                 polygon=None,
2518                 default_rate=None,
2519                 verbose=False):
2520        # Create object first to make area is available
2521        General_forcing.__init__(self,
2522                                 domain,
2523                                 'stage',
2524                                 rate=rate,
2525                                 center=center,
2526                                 radius=radius,
2527                                 polygon=polygon,
2528                                 default_rate=default_rate,
2529                                 verbose=verbose)
2530
2531    ##
2532    # @brief Update the instance rate.
2533    # @param t New rate object.
2534    def update_rate(self, t):
2535        """Virtual method allowing local modifications by writing an
2536        overriding version in descendant
2537
2538        This one converts m^3/s to m/s which can be added directly
2539        to 'stage' in ANUGA
2540        """
2541
2542        if callable(self.rate):
2543            _rate = self.rate(t)/self.exchange_area
2544        else:
2545            _rate = self.rate/self.exchange_area
2546
2547        return _rate
2548
2549
2550################################################################################
2551# Initialise module
2552################################################################################
2553
2554from anuga.utilities import compile
2555if compile.can_use_C_extension('shallow_water_ext.c'):
2556    # Underlying C implementations can be accessed
2557    from shallow_water_ext import rotate, assign_windfield_values
2558else:
2559    msg = 'C implementations could not be accessed by %s.\n ' % __file__
2560    msg += 'Make sure compile_all.py has been run as described in '
2561    msg += 'the ANUGA installation guide.'
2562    raise Exception, msg
2563
2564# Optimisation with psyco
2565from anuga.config import use_psyco
2566if use_psyco:
2567    try:
2568        import psyco
2569    except:
2570        import os
2571        if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
2572            pass
2573            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2574        else:
2575            msg = ('WARNING: psyco (speedup) could not be imported, '
2576                   'you may want to consider installing it')
2577            log.critical(msg)
2578    else:
2579        psyco.bind(Domain.distribute_to_vertices_and_edges)
2580        psyco.bind(Domain.compute_fluxes)
2581
2582
2583if __name__ == "__main__":
2584    pass
Note: See TracBrowser for help on using the repository browser.