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

Last change on this file since 7276 was 7276, checked in by ole, 15 years ago

Merged numpy branch back into the trunk.

In ~/sandpit/anuga/anuga_core/source
svn merge -r 6246:HEAD ../../branches/numpy .

In ~/sandpit/anuga/anuga_validation
svn merge -r 6417:HEAD ../branches/numpy_anuga_validation .

In ~/sandpit/anuga/misc
svn merge -r 6809:HEAD ../branches/numpy_misc .

For all merges, I used numpy version where conflicts existed

The suites test_all.py (in source/anuga) and validate_all.py passed using Python2.5 with numpy on my Ubuntu Linux box.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 89.1 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: 7276 $)
71ModifiedBy:
72    $Author: ole $
73    $Date: 2009-06-30 04:07:41 +0000 (Tue, 30 Jun 2009) $
74"""
75
76# Subversion keywords:
77#
78# $LastChangedDate: 2009-06-30 04:07:41 +0000 (Tue, 30 Jun 2009) $
79# $LastChangedRevision: 7276 $
80# $LastChangedBy: ole $
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
120
121from types import IntType, FloatType
122from warnings import warn
123
124
125################################################################################
126# Shallow water domain
127################################################################################
128
129##
130# @brief Class for a shallow water domain.
131class Domain(Generic_Domain):
132
133    conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
134    other_quantities = ['elevation', 'friction']
135
136    ##
137    # @brief Instantiate a shallow water domain.
138    # @param coordinates
139    # @param vertices
140    # @param boundary
141    # @param tagged_elements
142    # @param geo_reference
143    # @param use_inscribed_circle
144    # @param mesh_filename
145    # @param use_cache
146    # @param verbose
147    # @param full_send_dict
148    # @param ghost_recv_dict
149    # @param processor
150    # @param numproc
151    # @param number_of_full_nodes
152    # @param number_of_full_triangles
153    def __init__(self,
154                 coordinates=None,
155                 vertices=None,
156                 boundary=None,
157                 tagged_elements=None,
158                 geo_reference=None,
159                 use_inscribed_circle=False,
160                 mesh_filename=None,
161                 use_cache=False,
162                 verbose=False,
163                 full_send_dict=None,
164                 ghost_recv_dict=None,
165                 processor=0,
166                 numproc=1,
167                 number_of_full_nodes=None,
168                 number_of_full_triangles=None):
169
170        other_quantities = ['elevation', 'friction']
171        Generic_Domain.__init__(self,
172                                coordinates,
173                                vertices,
174                                boundary,
175                                Domain.conserved_quantities,
176                                Domain.other_quantities,
177                                tagged_elements,
178                                geo_reference,
179                                use_inscribed_circle,
180                                mesh_filename,
181                                use_cache,
182                                verbose,
183                                full_send_dict,
184                                ghost_recv_dict,
185                                processor,
186                                numproc,
187                                number_of_full_nodes=number_of_full_nodes,
188                                number_of_full_triangles=number_of_full_triangles)
189
190        self.set_minimum_allowed_height(minimum_allowed_height)
191
192        self.maximum_allowed_speed = maximum_allowed_speed
193        self.g = g
194        self.beta_w = beta_w
195        self.beta_w_dry = beta_w_dry
196        self.beta_uh = beta_uh
197        self.beta_uh_dry = beta_uh_dry
198        self.beta_vh = beta_vh
199        self.beta_vh_dry = beta_vh_dry
200        self.alpha_balance = alpha_balance
201
202        self.tight_slope_limiters = tight_slope_limiters
203        self.optimise_dry_cells = optimise_dry_cells
204
205        self.forcing_terms.append(manning_friction_implicit)
206        self.forcing_terms.append(gravity)
207
208        # Stored output
209        self.store = True
210        self.format = 'sww'
211        self.set_store_vertices_uniquely(False)
212        self.minimum_storable_height = minimum_storable_height
213        self.quantities_to_be_stored = ['stage', 'xmomentum', 'ymomentum']
214
215        # Limiters
216        self.use_edge_limiter = use_edge_limiter
217        self.optimised_gradient_limiter = optimised_gradient_limiter
218        self.use_centroid_velocities = use_centroid_velocities
219
220    ##
221    # @brief
222    # @param beta
223    def set_all_limiters(self, beta):
224        """Shorthand to assign one constant value [0,1] to all limiters.
225        0 Corresponds to first order, where as larger values make use of
226        the second order scheme.
227        """
228
229        self.beta_w = beta
230        self.beta_w_dry = beta
231        self.quantities['stage'].beta = beta
232
233        self.beta_uh = beta
234        self.beta_uh_dry = beta
235        self.quantities['xmomentum'].beta = beta
236
237        self.beta_vh = beta
238        self.beta_vh_dry = beta
239        self.quantities['ymomentum'].beta = beta
240
241    ##
242    # @brief
243    # @param flag
244    # @param reduction
245    def set_store_vertices_uniquely(self, flag, reduction=None):
246        """Decide whether vertex values should be stored uniquely as
247        computed in the model or whether they should be reduced to one
248        value per vertex using self.reduction.
249        """
250
251        # FIXME (Ole): how about using the word continuous vertex values?
252        self.smooth = not flag
253
254        # Reduction operation for get_vertex_values
255        if reduction is None:
256            self.reduction = mean
257            #self.reduction = min  #Looks better near steep slopes
258
259    ##
260    # @brief Set the minimum depth that will be written to an SWW file.
261    # @param minimum_storable_height The minimum stored height (in m).
262    def set_minimum_storable_height(self, minimum_storable_height):
263        """Set the minimum depth that will be recognised when writing
264        to an sww file. This is useful for removing thin water layers
265        that seems to be caused by friction creep.
266
267        The minimum allowed sww depth is in meters.
268        """
269
270        self.minimum_storable_height = minimum_storable_height
271
272    ##
273    # @brief
274    # @param minimum_allowed_height
275    def set_minimum_allowed_height(self, minimum_allowed_height):
276        """Set minimum depth that will be recognised in the numerical scheme.
277
278        The minimum allowed depth is in meters.
279
280        The parameter H0 (Minimal height for flux computation)
281        is also set by this function
282        """
283
284        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
285
286        #FIXME (Ole): Maybe use histogram to identify isolated extreme speeds
287        #and deal with them adaptively similarly to how we used to use 1 order
288        #steps to recover.
289
290        self.minimum_allowed_height = minimum_allowed_height
291        self.H0 = minimum_allowed_height
292
293    ##
294    # @brief
295    # @param maximum_allowed_speed
296    def set_maximum_allowed_speed(self, maximum_allowed_speed):
297        """Set the maximum particle speed that is allowed in water
298        shallower than minimum_allowed_height. This is useful for
299        controlling speeds in very thin layers of water and at the same time
300        allow some movement avoiding pooling of water.
301        """
302
303        self.maximum_allowed_speed = maximum_allowed_speed
304
305    ##
306    # @brief
307    # @param points_file_block_line_size
308    def set_points_file_block_line_size(self, points_file_block_line_size):
309        """Set the minimum depth that will be recognised when writing
310        to an sww file. This is useful for removing thin water layers
311        that seems to be caused by friction creep.
312
313        The minimum allowed sww depth is in meters.
314        """
315        self.points_file_block_line_size = points_file_block_line_size
316
317    ##
318    # @brief Set the quantities that will be written to an SWW file.
319    # @param q The quantities to be written.
320    # @note Param 'q' may be None, single quantity or list of quantity strings.
321    # @note If 'q' is None, no quantities will be stored in the SWW file.
322    def set_quantities_to_be_stored(self, q):
323        """Specify which quantities will be stored in the sww file.
324
325        q must be either:
326          - the name of a quantity
327          - a list of quantity names
328          - None
329
330        In the two first cases, the named quantities will be stored at
331        each yieldstep (This is in addition to the quantities elevation
332        and friction)
333
334        If q is None, storage will be switched off altogether.
335        """
336
337        if q is None:
338            self.quantities_to_be_stored = []
339            self.store = False
340            return
341
342        if isinstance(q, basestring):
343            q = [q] # Turn argument into a list
344
345        # Check correcness
346        for quantity_name in q:
347            msg = ('Quantity %s is not a valid conserved quantity'
348                   % quantity_name)
349            assert quantity_name in self.conserved_quantities, msg
350
351        self.quantities_to_be_stored = q
352
353    ##
354    # @brief
355    # @param indices
356    def get_wet_elements(self, indices=None):
357        """Return indices for elements where h > minimum_allowed_height
358
359        Optional argument:
360            indices is the set of element ids that the operation applies to.
361
362        Usage:
363            indices = get_wet_elements()
364
365        Note, centroid values are used for this operation
366        """
367
368        # Water depth below which it is considered to be 0 in the model
369        # FIXME (Ole): Allow this to be specified as a keyword argument as well
370        from anuga.config import minimum_allowed_height
371
372        elevation = self.get_quantity('elevation').\
373                        get_values(location='centroids', indices=indices)
374        stage = self.get_quantity('stage').\
375                    get_values(location='centroids', indices=indices)
376        depth = stage - elevation
377
378        # Select indices for which depth > 0
379        wet_indices = num.compress(depth > minimum_allowed_height,
380                                   num.arange(len(depth)))
381        return wet_indices
382
383    ##
384    # @brief
385    # @param indices
386    def get_maximum_inundation_elevation(self, indices=None):
387        """Return highest elevation where h > 0
388
389        Optional argument:
390            indices is the set of element ids that the operation applies to.
391
392        Usage:
393            q = get_maximum_inundation_elevation()
394
395        Note, centroid values are used for this operation
396        """
397
398        wet_elements = self.get_wet_elements(indices)
399        return self.get_quantity('elevation').\
400                   get_maximum_value(indices=wet_elements)
401
402    ##
403    # @brief
404    # @param indices
405    def get_maximum_inundation_location(self, indices=None):
406        """Return location of highest elevation where h > 0
407
408        Optional argument:
409            indices is the set of element ids that the operation applies to.
410
411        Usage:
412            q = get_maximum_inundation_location()
413
414        Note, centroid values are used for this operation
415        """
416
417        wet_elements = self.get_wet_elements(indices)
418        return self.get_quantity('elevation').\
419                   get_maximum_location(indices=wet_elements)
420
421    ##
422    # @brief Get the total flow through an arbitrary poly line.
423    # @param polyline Representation of desired cross section.
424    # @param verbose True if this method is to be verbose.
425    # @note 'polyline' may contain multiple sections allowing complex shapes.
426    # @note Assume absolute UTM coordinates.
427    def get_flow_through_cross_section(self, polyline, verbose=False):
428        """Get the total flow through an arbitrary poly line.
429
430        This is a run-time equivalent of the function with same name
431        in data_manager.py
432
433        Input:
434            polyline: Representation of desired cross section - it may contain
435                      multiple sections allowing for complex shapes. Assume
436                      absolute UTM coordinates.
437                      Format [[x0, y0], [x1, y1], ...]
438
439        Output:
440            Q: Total flow [m^3/s] across given segments.
441        """
442
443        # Find all intersections and associated triangles.
444        segments = self.get_intersecting_segments(polyline, use_cache=True,
445                                                  verbose=verbose)
446
447        # Get midpoints
448        midpoints = segment_midpoints(segments)
449
450        # Make midpoints Geospatial instances
451        midpoints = ensure_geospatial(midpoints, self.geo_reference)
452
453        # Compute flow
454        if verbose: print 'Computing flow through specified cross section'
455
456        # Get interpolated values
457        xmomentum = self.get_quantity('xmomentum')
458        ymomentum = self.get_quantity('ymomentum')
459
460        uh = xmomentum.get_values(interpolation_points=midpoints,
461                                  use_cache=True)
462        vh = ymomentum.get_values(interpolation_points=midpoints,
463                                  use_cache=True)
464
465        # Compute and sum flows across each segment
466        total_flow = 0
467        for i in range(len(uh)):
468            # Inner product of momentum vector with segment normal [m^2/s]
469            normal = segments[i].normal
470            normal_momentum = uh[i]*normal[0] + vh[i]*normal[1]
471
472            # Flow across this segment [m^3/s]
473            segment_flow = normal_momentum*segments[i].length
474
475            # Accumulate
476            total_flow += segment_flow
477
478        return total_flow
479
480    ##
481    # @brief
482    # @param polyline Representation of desired cross section.
483    # @param kind Select energy type to compute ('specific' or 'total').
484    # @param verbose True if this method is to be verbose.
485    # @note 'polyline' may contain multiple sections allowing complex shapes.
486    # @note Assume absolute UTM coordinates.
487    def get_energy_through_cross_section(self, polyline,
488                                         kind='total',
489                                         verbose=False):
490        """Obtain average energy head [m] across specified cross section.
491
492        Inputs:
493            polyline: Representation of desired cross section - it may contain
494                      multiple sections allowing for complex shapes. Assume
495                      absolute UTM coordinates.
496                      Format [[x0, y0], [x1, y1], ...]
497            kind:     Select which energy to compute.
498                      Options are 'specific' and 'total' (default)
499
500        Output:
501            E: Average energy [m] across given segments for all stored times.
502
503        The average velocity is computed for each triangle intersected by
504        the polyline and averaged weighted by segment lengths.
505
506        The typical usage of this function would be to get average energy of
507        flow in a channel, and the polyline would then be a cross section
508        perpendicular to the flow.
509
510        #FIXME (Ole) - need name for this energy reflecting that its dimension
511        is [m].
512        """
513
514        from anuga.config import g, epsilon, velocity_protection as h0
515
516        # Find all intersections and associated triangles.
517        segments = self.get_intersecting_segments(polyline, use_cache=True,
518                                                  verbose=verbose)
519
520        # Get midpoints
521        midpoints = segment_midpoints(segments)
522
523        # Make midpoints Geospatial instances
524        midpoints = ensure_geospatial(midpoints, self.geo_reference)
525
526        # Compute energy
527        if verbose: print 'Computing %s energy' %kind
528
529        # Get interpolated values
530        stage = self.get_quantity('stage')
531        elevation = self.get_quantity('elevation')
532        xmomentum = self.get_quantity('xmomentum')
533        ymomentum = self.get_quantity('ymomentum')
534
535        w = stage.get_values(interpolation_points=midpoints, use_cache=True)
536        z = elevation.get_values(interpolation_points=midpoints, use_cache=True)
537        uh = xmomentum.get_values(interpolation_points=midpoints,
538                                  use_cache=True)
539        vh = ymomentum.get_values(interpolation_points=midpoints,
540                                  use_cache=True)
541        h = w-z                # Depth
542
543        # Compute total length of polyline for use with weighted averages
544        total_line_length = 0.0
545        for segment in segments:
546            total_line_length += segment.length
547
548        # Compute and sum flows across each segment
549        average_energy = 0.0
550        for i in range(len(w)):
551            # Average velocity across this segment
552            if h[i] > epsilon:
553                # Use protection against degenerate velocities
554                u = uh[i]/(h[i] + h0/h[i])
555                v = vh[i]/(h[i] + h0/h[i])
556            else:
557                u = v = 0.0
558
559            speed_squared = u*u + v*v
560            kinetic_energy = 0.5*speed_squared/g
561
562            if kind == 'specific':
563                segment_energy = h[i] + kinetic_energy
564            elif kind == 'total':
565                segment_energy = w[i] + kinetic_energy
566            else:
567                msg = 'Energy kind must be either "specific" or "total".'
568                msg += ' I got %s' %kind
569
570            # Add to weighted average
571            weigth = segments[i].length/total_line_length
572            average_energy += segment_energy*weigth
573
574        return average_energy
575
576    ##
577    # @brief Run integrity checks on shallow water domain.
578    def check_integrity(self):
579        Generic_Domain.check_integrity(self)
580
581        #Check that we are solving the shallow water wave equation
582        msg = 'First conserved quantity must be "stage"'
583        assert self.conserved_quantities[0] == 'stage', msg
584        msg = 'Second conserved quantity must be "xmomentum"'
585        assert self.conserved_quantities[1] == 'xmomentum', msg
586        msg = 'Third conserved quantity must be "ymomentum"'
587        assert self.conserved_quantities[2] == 'ymomentum', msg
588
589    ##
590    # @brief
591    def extrapolate_second_order_sw(self):
592        #Call correct module function (either from this module or C-extension)
593        extrapolate_second_order_sw(self)
594
595    ##
596    # @brief
597    def compute_fluxes(self):
598        #Call correct module function (either from this module or C-extension)
599        compute_fluxes(self)
600
601    ##
602    # @brief
603    def distribute_to_vertices_and_edges(self):
604        # Call correct module function
605        if self.use_edge_limiter:
606            distribute_using_edge_limiter(self)
607        else:
608            distribute_using_vertex_limiter(self)
609
610    ##
611    # @brief Evolve the model by one step.
612    # @param yieldstep
613    # @param finaltime
614    # @param duration
615    # @param skip_initial_step
616    def evolve(self,
617               yieldstep=None,
618               finaltime=None,
619               duration=None,
620               skip_initial_step=False):
621        """Specialisation of basic evolve method from parent class"""
622
623        # Call check integrity here rather than from user scripts
624        # self.check_integrity()
625
626        msg = 'Attribute self.beta_w must be in the interval [0, 2]'
627        assert 0 <= self.beta_w <= 2.0, msg
628
629        # Initial update of vertex and edge values before any STORAGE
630        # and or visualisation.
631        # This is done again in the initialisation of the Generic_Domain
632        # evolve loop but we do it here to ensure the values are ok for storage.
633        self.distribute_to_vertices_and_edges()
634
635        if self.store is True and self.time == 0.0:
636            self.initialise_storage()
637        else:
638            pass
639            # print 'Results will not be stored.'
640            # print 'To store results set domain.store = True'
641            # FIXME: Diagnostic output should be controlled by
642            # a 'verbose' flag living in domain (or in a parent class)
643
644        # Call basic machinery from parent class
645        for t in Generic_Domain.evolve(self, yieldstep=yieldstep,
646                                       finaltime=finaltime, duration=duration,
647                                       skip_initial_step=skip_initial_step):
648            # Store model data, e.g. for subsequent visualisation
649            if self.store is True:
650                self.store_timestep(self.quantities_to_be_stored)
651
652            # FIXME: Could maybe be taken from specified list
653            # of 'store every step' quantities
654
655            # Pass control on to outer loop for more specific actions
656            yield(t)
657
658    ##
659    # @brief
660    def initialise_storage(self):
661        """Create and initialise self.writer object for storing data.
662        Also, save x,y and bed elevation
663        """
664
665        from anuga.shallow_water.data_manager import get_dataobject
666
667        # Initialise writer
668        self.writer = get_dataobject(self, mode=netcdf_mode_w)
669
670        # Store vertices and connectivity
671        self.writer.store_connectivity()
672
673    ##
674    # @brief
675    # @param name
676    def store_timestep(self, name):
677        """Store named quantity and time.
678
679        Precondition:
680           self.write has been initialised
681        """
682
683        self.writer.store_timestep(name)
684
685    ##
686    # @brief Get time stepping statistics string for printing.
687    # @param track_speeds
688    # @param triangle_id
689    def timestepping_statistics(self,
690                                track_speeds=False,
691                                triangle_id=None):
692        """Return string with time stepping statistics for printing or logging
693
694        Optional boolean keyword track_speeds decides whether to report
695        location of smallest timestep as well as a histogram and percentile
696        report.
697        """
698
699        from anuga.config import epsilon, g
700
701        # Call basic machinery from parent class
702        msg = Generic_Domain.timestepping_statistics(self, track_speeds,
703                                                     triangle_id)
704
705        if track_speeds is True:
706            # qwidth determines the text field used for quantities
707            qwidth = self.qwidth
708
709            # Selected triangle
710            k = self.k
711
712            # Report some derived quantities at vertices, edges and centroid
713            # specific to the shallow water wave equation
714            z = self.quantities['elevation']
715            w = self.quantities['stage']
716
717            Vw = w.get_values(location='vertices', indices=[k])[0]
718            Ew = w.get_values(location='edges', indices=[k])[0]
719            Cw = w.get_values(location='centroids', indices=[k])
720
721            Vz = z.get_values(location='vertices', indices=[k])[0]
722            Ez = z.get_values(location='edges', indices=[k])[0]
723            Cz = z.get_values(location='centroids', indices=[k])
724
725            name = 'depth'
726            Vh = Vw-Vz
727            Eh = Ew-Ez
728            Ch = Cw-Cz
729
730            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
731                 %(name.ljust(qwidth), Vh[0], Vh[1], Vh[2])
732
733            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
734                 %(name.ljust(qwidth), Eh[0], Eh[1], Eh[2])
735
736            s += '    %s: centroid_value = %.4f\n'\
737                 %(name.ljust(qwidth), Ch[0])
738
739            msg += s
740
741            uh = self.quantities['xmomentum']
742            vh = self.quantities['ymomentum']
743
744            Vuh = uh.get_values(location='vertices', indices=[k])[0]
745            Euh = uh.get_values(location='edges', indices=[k])[0]
746            Cuh = uh.get_values(location='centroids', indices=[k])
747
748            Vvh = vh.get_values(location='vertices', indices=[k])[0]
749            Evh = vh.get_values(location='edges', indices=[k])[0]
750            Cvh = vh.get_values(location='centroids', indices=[k])
751
752            # Speeds in each direction
753            Vu = Vuh/(Vh + epsilon)
754            Eu = Euh/(Eh + epsilon)
755            Cu = Cuh/(Ch + epsilon)
756            name = 'U'
757            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
758                 %(name.ljust(qwidth), Vu[0], Vu[1], Vu[2])
759
760            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
761                 %(name.ljust(qwidth), Eu[0], Eu[1], Eu[2])
762
763            s += '    %s: centroid_value = %.4f\n'\
764                 %(name.ljust(qwidth), Cu[0])
765
766            msg += s
767
768            Vv = Vvh/(Vh + epsilon)
769            Ev = Evh/(Eh + epsilon)
770            Cv = Cvh/(Ch + epsilon)
771            name = 'V'
772            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
773                 %(name.ljust(qwidth), Vv[0], Vv[1], Vv[2])
774
775            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
776                 %(name.ljust(qwidth), Ev[0], Ev[1], Ev[2])
777
778            s += '    %s: centroid_value = %.4f\n'\
779                 %(name.ljust(qwidth), Cv[0])
780
781            msg += s
782
783            # Froude number in each direction
784            name = 'Froude (x)'
785            Vfx = Vu/(num.sqrt(g*Vh) + epsilon)
786            Efx = Eu/(num.sqrt(g*Eh) + epsilon)
787            Cfx = Cu/(num.sqrt(g*Ch) + epsilon)
788
789            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
790                 %(name.ljust(qwidth), Vfx[0], Vfx[1], Vfx[2])
791
792            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
793                 %(name.ljust(qwidth), Efx[0], Efx[1], Efx[2])
794
795            s += '    %s: centroid_value = %.4f\n'\
796                 %(name.ljust(qwidth), Cfx[0])
797
798            msg += s
799
800            name = 'Froude (y)'
801            Vfy = Vv/(num.sqrt(g*Vh) + epsilon)
802            Efy = Ev/(num.sqrt(g*Eh) + epsilon)
803            Cfy = Cv/(num.sqrt(g*Ch) + epsilon)
804
805            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
806                 %(name.ljust(qwidth), Vfy[0], Vfy[1], Vfy[2])
807
808            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
809                 %(name.ljust(qwidth), Efy[0], Efy[1], Efy[2])
810
811            s += '    %s: centroid_value = %.4f\n'\
812                 %(name.ljust(qwidth), Cfy[0])
813
814            msg += s
815
816        return msg
817       
818       
819
820    def compute_boundary_flows(self):
821        """Compute boundary flows at current timestep.
822                       
823        Quantities computed are:
824           Total inflow across boundary
825           Total outflow across boundary
826           Flow across each tagged boundary segment
827        """
828               
829        # Run through boundary array and compute for each segment
830        # the normal momentum ((uh, vh) dot normal) times segment length.
831        # Based on sign accumulate this into boundary_inflow and boundary_outflow.
832                       
833        # Compute flows along boundary
834       
835        uh = self.get_quantity('xmomentum').get_values(location='edges')
836        vh = self.get_quantity('ymomentum').get_values(location='edges')       
837       
838        # Loop through edges that lie on the boundary and calculate
839        # flows
840        boundary_flows = {}
841        total_boundary_inflow = 0.0
842        total_boundary_outflow = 0.0
843        for vol_id, edge_id in self.boundary:
844            # Compute normal flow across edge. Since normal vector points
845            # away from triangle, a positive sign means that water
846            # flows *out* from this triangle.
847             
848            momentum = [uh[vol_id, edge_id], vh[vol_id, edge_id]]
849            normal = self.mesh.get_normal(vol_id, edge_id)
850            length = self.mesh.get_edgelength(vol_id, edge_id)           
851            normal_flow = num.dot(momentum, normal)*length
852           
853            # Reverse sign so that + is taken to mean inflow
854            # and - means outflow. This is more intuitive.
855            edge_flow = -normal_flow
856           
857            # Tally up inflows and outflows separately
858            if edge_flow > 0:
859                # Flow is inflow     
860                total_boundary_inflow += edge_flow                                 
861            else:
862                # Flow is outflow
863                total_boundary_outflow += edge_flow   
864
865            # Tally up flows by boundary tag
866            tag = self.boundary[(vol_id, edge_id)]
867           
868            if tag not in boundary_flows:
869                boundary_flows[tag] = 0.0
870            boundary_flows[tag] += edge_flow
871           
872               
873        return boundary_flows, total_boundary_inflow, total_boundary_outflow
874       
875
876    def compute_forcing_flows(self):
877        """Compute flows in and out of domain due to forcing terms.
878                       
879        Quantities computed are:
880               
881       
882           Total inflow through forcing terms
883           Total outflow through forcing terms
884           Current total volume in domain       
885
886        """
887
888        #FIXME(Ole): We need to separate what part of explicit_update was
889        # due to the normal flux calculations and what is due to forcing terms.
890       
891        pass
892                       
893       
894    def compute_total_volume(self):
895        """Compute total volume (m^3) of water in entire domain
896        """
897       
898        area = self.mesh.get_areas()
899        volume = 0.0
900       
901        stage = self.get_quantity('stage').get_values(location='centroids')
902        elevation = self.get_quantity('elevation').get_values(location='centroids')       
903        depth = stage-elevation
904       
905        #print 'z', elevation
906        #print 'w', stage
907        #print 'h', depth
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                print 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            print model_data.basename+' >>>> WARNING: computed depth_dependent friction out of range ddf,n1,n2 ', depth_dependent_friction, n1,n2
1840       
1841        # update depth dependent friction  for that wet element
1842        wet_friction[i] = depth_dependent_friction
1843       
1844    # EHR add code to show range of 'friction across domain at this instant as sanity check?????????
1845   
1846    if verbose :
1847        nvals=domain.get_quantity('friction').get_values(location='centroids')        # return array of domain nvals
1848        n_min=min(nvals)
1849        n_max=max(nvals)
1850       
1851        print "         ++++ calculate_depth_dependent_friction  - Updated friction - range  %7.3f to %7.3f" %(n_min,n_max)
1852   
1853    return wet_friction
1854
1855
1856################################################################################
1857# Experimental auxiliary functions
1858################################################################################
1859
1860##
1861# @brief Check forcefield parameter.
1862# @param f Object to check.
1863# @note 'f' may be a callable object or a scalar value.
1864def check_forcefield(f):
1865    """Check that force object is as expected.
1866   
1867    Check that f is either:
1868    1: a callable object f(t,x,y), where x and y are vectors
1869       and that it returns an array or a list of same length
1870       as x and y
1871    2: a scalar
1872    """
1873
1874    if callable(f):
1875        N = 3
1876        x = num.ones(3, num.float)
1877        y = num.ones(3, num.float)
1878        try:
1879            q = f(1.0, x=x, y=y)
1880        except Exception, e:
1881            msg = 'Function %s could not be executed:\n%s' %(f, e)
1882            # FIXME: Reconsider this semantics
1883            raise Exception, msg
1884
1885        try:
1886            q = num.array(q, num.float)
1887        except:
1888            msg = ('Return value from vector function %s could not '
1889                   'be converted into a numeric array of floats.\nSpecified '
1890                   'function should return either list or array.' % f)
1891            raise Exception, msg
1892
1893        # Is this really what we want?
1894        # info is "(func name, filename, defining line)"
1895        func_info = (f.func_name, f.func_code.co_filename,
1896                     f.func_code.co_firstlineno)
1897        func_msg = 'Function %s (defined in %s, line %d)' % func_info
1898        try:
1899            result_len = len(q)
1900        except:
1901            msg = '%s must return vector' % func_msg
1902            self.fail(msg)
1903        msg = '%s must return vector of length %d' % (func_msg, N)
1904        assert result_len == N, msg
1905    else:
1906        try:
1907            f = float(f)
1908        except:
1909            msg = ('Force field %s must be a scalar value coercible to float.'
1910                   % str(f))
1911            raise Exception, msg
1912
1913    return f
1914
1915
1916##
1917# Class to apply a wind stress to a domain.
1918class Wind_stress:
1919    """Apply wind stress to water momentum in terms of
1920    wind speed [m/s] and wind direction [degrees]
1921    """
1922
1923    ##
1924    # @brief Create an instance of Wind_stress.
1925    # @param *args
1926    # @param **kwargs
1927    def __init__(self, *args, **kwargs):
1928        """Initialise windfield from wind speed s [m/s]
1929        and wind direction phi [degrees]
1930
1931        Inputs v and phi can be either scalars or Python functions, e.g.
1932
1933        W = Wind_stress(10, 178)
1934
1935        #FIXME - 'normal' degrees are assumed for now, i.e. the
1936        vector (1,0) has zero degrees.
1937        We may need to convert from 'compass' degrees later on and also
1938        map from True north to grid north.
1939
1940        Arguments can also be Python functions of t,x,y as in
1941
1942        def speed(t,x,y):
1943            ...
1944            return s
1945
1946        def angle(t,x,y):
1947            ...
1948            return phi
1949
1950        where x and y are vectors.
1951
1952        and then pass the functions in
1953
1954        W = Wind_stress(speed, angle)
1955
1956        The instantiated object W can be appended to the list of
1957        forcing_terms as in
1958
1959        Alternatively, one vector valued function for (speed, angle)
1960        can be applied, providing both quantities simultaneously.
1961        As in
1962        W = Wind_stress(F), where returns (speed, angle) for each t.
1963
1964        domain.forcing_terms.append(W)
1965        """
1966
1967        from anuga.config import rho_a, rho_w, eta_w
1968
1969        if len(args) == 2:
1970            s = args[0]
1971            phi = args[1]
1972        elif len(args) == 1:
1973            # Assume vector function returning (s, phi)(t,x,y)
1974            vector_function = args[0]
1975            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1976            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1977        else:
1978           # Assume info is in 2 keyword arguments
1979           if len(kwargs) == 2:
1980               s = kwargs['s']
1981               phi = kwargs['phi']
1982           else:
1983               raise Exception, 'Assumes two keyword arguments: s=..., phi=....'
1984
1985        self.speed = check_forcefield(s)
1986        self.phi = check_forcefield(phi)
1987
1988        self.const = eta_w*rho_a/rho_w
1989
1990    ##
1991    # @brief 'execute' this class instance.
1992    # @param domain
1993    def __call__(self, domain):
1994        """Evaluate windfield based on values found in domain"""
1995
1996        from math import pi, cos, sin, sqrt
1997
1998        xmom_update = domain.quantities['xmomentum'].explicit_update
1999        ymom_update = domain.quantities['ymomentum'].explicit_update
2000
2001        N = len(domain)    # number_of_triangles
2002        t = domain.time
2003
2004        if callable(self.speed):
2005            xc = domain.get_centroid_coordinates()
2006            s_vec = self.speed(t, xc[:,0], xc[:,1])
2007        else:
2008            # Assume s is a scalar
2009            try:
2010                s_vec = self.speed * num.ones(N, num.float)
2011            except:
2012                msg = 'Speed must be either callable or a scalar: %s' %self.s
2013                raise msg
2014
2015        if callable(self.phi):
2016            xc = domain.get_centroid_coordinates()
2017            phi_vec = self.phi(t, xc[:,0], xc[:,1])
2018        else:
2019            # Assume phi is a scalar
2020
2021            try:
2022                phi_vec = self.phi * num.ones(N, num.float)
2023            except:
2024                msg = 'Angle must be either callable or a scalar: %s' %self.phi
2025                raise msg
2026
2027        assign_windfield_values(xmom_update, ymom_update,
2028                                s_vec, phi_vec, self.const)
2029
2030
2031##
2032# @brief Assign wind field values
2033# @param xmom_update
2034# @param ymom_update
2035# @param s_vec
2036# @param phi_vec
2037# @param const
2038def assign_windfield_values(xmom_update, ymom_update,
2039                            s_vec, phi_vec, const):
2040    """Python version of assigning wind field to update vectors.
2041    A C version also exists (for speed)
2042    """
2043
2044    from math import pi, cos, sin, sqrt
2045
2046    N = len(s_vec)
2047    for k in range(N):
2048        s = s_vec[k]
2049        phi = phi_vec[k]
2050
2051        # Convert to radians
2052        phi = phi*pi/180
2053
2054        # Compute velocity vector (u, v)
2055        u = s*cos(phi)
2056        v = s*sin(phi)
2057
2058        # Compute wind stress
2059        S = const * sqrt(u**2 + v**2)
2060        xmom_update[k] += S*u
2061        ymom_update[k] += S*v
2062
2063
2064##
2065# @brief A class for a general explicit forcing term.
2066class General_forcing:
2067    """General explicit forcing term for update of quantity
2068
2069    This is used by Inflow and Rainfall for instance
2070
2071
2072    General_forcing(quantity_name, rate, center, radius, polygon)
2073
2074    domain:     ANUGA computational domain
2075    quantity_name: Name of quantity to update.
2076                   It must be a known conserved quantity.
2077
2078    rate [?/s]: Total rate of change over the specified area.
2079                This parameter can be either a constant or a
2080                function of time. Positive values indicate increases,
2081                negative values indicate decreases.
2082                Rate can be None at initialisation but must be specified
2083                before forcing term is applied (i.e. simulation has started).
2084
2085    center [m]: Coordinates at center of flow point
2086    radius [m]: Size of circular area
2087    polygon:    Arbitrary polygon
2088    default_rate: Rate to be used if rate fails (e.g. if model time exceeds its data)
2089                  Admissible types: None, constant number or function of t
2090
2091
2092    Either center, radius or polygon can be specified but not both.
2093    If neither are specified the entire domain gets updated.
2094    All coordinates to be specified in absolute UTM coordinates (x, y) assuming the zone of domain.
2095
2096    Inflow or Rainfall for examples of use
2097    """
2098
2099
2100    # FIXME (AnyOne) : Add various methods to allow spatial variations
2101
2102    ##
2103    # @brief Create an instance of this forcing term.
2104    # @param domain
2105    # @param quantity_name
2106    # @param rate
2107    # @param center
2108    # @param radius
2109    # @param polygon
2110    # @param default_rate
2111    # @param verbose
2112    def __init__(self,
2113                 domain,
2114                 quantity_name,
2115                 rate=0.0,
2116                 center=None,
2117                 radius=None,
2118                 polygon=None,
2119                 default_rate=None,
2120                 verbose=False):
2121
2122        from math import pi, cos, sin
2123
2124        if center is None:
2125            msg = 'I got radius but no center.'
2126            assert radius is None, msg
2127
2128        if radius is None:
2129            msg += 'I got center but no radius.'
2130            assert center is None, msg
2131
2132        self.domain = domain
2133        self.quantity_name = quantity_name
2134        self.rate = rate
2135        self.center = ensure_numeric(center)
2136        self.radius = radius
2137        self.polygon = polygon
2138        self.verbose = verbose
2139        self.value = 0.0    # Can be used to remember value at
2140                            # previous timestep in order to obtain rate
2141
2142        # Get boundary (in absolute coordinates)
2143        bounding_polygon = domain.get_boundary_polygon()
2144
2145        # Update area if applicable
2146        if center is not None and radius is not None:
2147            assert len(center) == 2
2148            msg = 'Polygon cannot be specified when center and radius are'
2149            assert polygon is None, msg
2150
2151            # Check that circle center lies within the mesh.
2152            msg = 'Center %s specified for forcing term did not' % str(center)
2153            msg += 'fall within the domain boundary.'
2154            assert is_inside_polygon(center, bounding_polygon), msg
2155
2156            # Check that circle periphery lies within the mesh.
2157            N = 100
2158            periphery_points = []
2159            for i in range(N):
2160                theta = 2*pi*i/100
2161
2162                x = center[0] + radius*cos(theta)
2163                y = center[1] + radius*sin(theta)
2164
2165                periphery_points.append([x,y])
2166
2167            for point in periphery_points:
2168                msg = 'Point %s on periphery for forcing term' % str(point)
2169                msg += ' did not fall within the domain boundary.'
2170                assert is_inside_polygon(point, bounding_polygon), msg
2171
2172        if polygon is not None:
2173            # Check that polygon lies within the mesh.
2174            for point in self.polygon:
2175                msg = 'Point %s in polygon for forcing term' % str(point)
2176                msg += ' did not fall within the domain boundary.'
2177                assert is_inside_polygon(point, bounding_polygon), msg
2178
2179        # Pointer to update vector
2180        self.update = domain.quantities[self.quantity_name].explicit_update
2181
2182        # Determine indices in flow area
2183        N = len(domain)
2184        points = domain.get_centroid_coordinates(absolute=True)
2185
2186        # Calculate indices in exchange area for this forcing term
2187        self.exchange_indices = None
2188        if self.center is not None and self.radius is not None:
2189            # Inlet is circular
2190            inlet_region = 'center=%s, radius=%s' % (self.center, self.radius)
2191
2192            self.exchange_indices = []
2193            for k in range(N):
2194                x, y = points[k,:]    # Centroid
2195
2196                c = self.center
2197                if ((x-c[0])**2+(y-c[1])**2) < self.radius**2:
2198                    self.exchange_indices.append(k)
2199
2200        if self.polygon is not None:
2201            # Inlet is polygon
2202            inlet_region = 'polygon=%s' % (self.polygon) 
2203            self.exchange_indices = inside_polygon(points, self.polygon)
2204
2205        if self.exchange_indices is None:
2206            self.exchange_area = polygon_area(bounding_polygon)
2207        else:   
2208            if len(self.exchange_indices) == 0:
2209                msg = 'No triangles have been identified in '
2210                msg += 'specified region: %s' % inlet_region
2211                raise Exception, msg
2212
2213            # Compute exchange area as the sum of areas of triangles identified
2214            # by circle or polygon
2215            self.exchange_area = 0.0
2216            for i in self.exchange_indices:
2217                self.exchange_area += domain.areas[i]
2218           
2219
2220        msg = 'Exchange area in forcing term'
2221        msg += ' has area = %f' %self.exchange_area
2222        assert self.exchange_area > 0.0           
2223           
2224               
2225
2226           
2227        # Check and store default_rate
2228        msg = ('Keyword argument default_rate must be either None '
2229               'or a function of time.\nI got %s.' % str(default_rate))
2230        assert (default_rate is None or
2231                type(default_rate) in [IntType, FloatType] or
2232                callable(default_rate)), msg
2233
2234        if default_rate is not None:
2235            # If it is a constant, make it a function
2236            if not callable(default_rate):
2237                tmp = default_rate
2238                default_rate = lambda t: tmp
2239
2240            # Check that default_rate is a function of one argument
2241            try:
2242                default_rate(0.0)
2243            except:
2244                raise Exception, msg
2245
2246        self.default_rate = default_rate
2247        self.default_rate_invoked = False    # Flag
2248
2249    ##
2250    # @brief Execute this instance.
2251    # @param domain
2252    def __call__(self, domain):
2253        """Apply inflow function at time specified in domain, update stage"""
2254
2255        # Call virtual method allowing local modifications
2256        t = domain.get_time()
2257        try:
2258            rate = self.update_rate(t)
2259        except Modeltime_too_early, e:
2260            raise Modeltime_too_early, e
2261        except Modeltime_too_late, e:
2262            if self.default_rate is None:
2263                raise Exception, e    # Reraise exception
2264            else:
2265                # Pass control to default rate function
2266                rate = self.default_rate(t)
2267
2268                if self.default_rate_invoked is False:
2269                    # Issue warning the first time
2270                    msg = ('%s\n'
2271                           'Instead I will use the default rate: %s\n'
2272                           'Note: Further warnings will be supressed'
2273                           % (str(e), str(self.default_rate)))
2274                    warn(msg)
2275
2276                    # FIXME (Ole): Replace this crude flag with
2277                    # Python's ability to print warnings only once.
2278                    # See http://docs.python.org/lib/warning-filter.html
2279                    self.default_rate_invoked = True
2280
2281        if rate is None:
2282            msg = ('Attribute rate must be specified in General_forcing '
2283                   'or its descendants before attempting to call it')
2284            raise Exception, msg
2285
2286        # Now rate is a number
2287        if self.verbose is True:
2288            print 'Rate of %s at time = %.2f = %f' % (self.quantity_name,
2289                                                      domain.get_time(),
2290                                                      rate)
2291
2292        if self.exchange_indices is None:
2293            self.update[:] += rate
2294        else:
2295            # Brute force assignment of restricted rate
2296            for k in self.exchange_indices:
2297                self.update[k] += rate
2298
2299    ##
2300    # @brief Update the internal rate.
2301    # @param t A callable or scalar used to set the rate.
2302    # @return The new rate.
2303    def update_rate(self, t):
2304        """Virtual method allowing local modifications by writing an
2305        overriding version in descendant
2306        """
2307
2308        if callable(self.rate):
2309            rate = self.rate(t)
2310        else:
2311            rate = self.rate
2312
2313        return rate
2314
2315    ##
2316    # @brief Get values for the specified quantity.
2317    # @param quantity_name Name of the quantity of interest.
2318    # @return The value(s) of the quantity.
2319    # @note If 'quantity_name' is None, use self.quantity_name.
2320    def get_quantity_values(self, quantity_name=None):
2321        """Return values for specified quantity restricted to opening
2322
2323        Optionally a quantity name can be specified if values from another
2324        quantity is sought
2325        """
2326
2327        if quantity_name is None:
2328            quantity_name = self.quantity_name
2329
2330        q = self.domain.quantities[quantity_name]
2331        return q.get_values(location='centroids',
2332                            indices=self.exchange_indices)
2333
2334    ##
2335    # @brief Set value for the specified quantity.
2336    # @param val The value object used to set value.
2337    # @param quantity_name Name of the quantity of interest.
2338    # @note If 'quantity_name' is None, use self.quantity_name.
2339    def set_quantity_values(self, val, quantity_name=None):
2340        """Set values for specified quantity restricted to opening
2341
2342        Optionally a quantity name can be specified if values from another
2343        quantity is sought
2344        """
2345
2346        if quantity_name is None:
2347            quantity_name = self.quantity_name
2348
2349        q = self.domain.quantities[self.quantity_name]
2350        q.set_values(val,
2351                     location='centroids',
2352                     indices=self.exchange_indices)
2353
2354
2355##
2356# @brief A class for rainfall forcing function.
2357# @note Inherits from General_forcing.
2358class Rainfall(General_forcing):
2359    """Class Rainfall - general 'rain over entire domain' forcing term.
2360
2361    Used for implementing Rainfall over the entire domain.
2362
2363        Current Limited to only One Gauge..
2364
2365        Need to add Spatial Varying Capability
2366        (This module came from copying and amending the Inflow Code)
2367
2368    Rainfall(rain)
2369
2370    domain
2371    rain [mm/s]:  Total rain rate over the specified domain.
2372                  NOTE: Raingauge Data needs to reflect the time step.
2373                  IE: if Gauge is mm read at a time step, then the input
2374                  here is as mm/(timeStep) so 10mm in 5minutes becomes
2375                  10/(5x60) = 0.0333mm/s.
2376
2377                  This parameter can be either a constant or a
2378                  function of time. Positive values indicate inflow,
2379                  negative values indicate outflow.
2380                  (and be used for Infiltration - Write Seperate Module)
2381                  The specified flow will be divided by the area of
2382                  the inflow region and then applied to update the
2383                  stage quantity.
2384
2385    polygon: Specifies a polygon to restrict the rainfall.
2386
2387    Examples
2388    How to put them in a run File...
2389
2390    #------------------------------------------------------------------------
2391    # Setup specialised forcing terms
2392    #------------------------------------------------------------------------
2393    # This is the new element implemented by Ole and Rudy to allow direct
2394    # input of Rainfall in mm/s
2395
2396    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms'))
2397                        # Note need path to File in String.
2398                        # Else assumed in same directory
2399
2400    domain.forcing_terms.append(catchmentrainfall)
2401    """
2402
2403    ##
2404    # @brief Create an instance of the class.
2405    # @param domain Domain of interest.
2406    # @param rate Total rain rate over the specified domain (mm/s).
2407    # @param center
2408    # @param radius
2409    # @param polygon Polygon  to restrict rainfall.
2410    # @param default_rate
2411    # @param verbose True if this instance is to be verbose.
2412    def __init__(self,
2413                 domain,
2414                 rate=0.0,
2415                 center=None,
2416                 radius=None,
2417                 polygon=None,
2418                 default_rate=None,
2419                 verbose=False):
2420
2421        # Converting mm/s to m/s to apply in ANUGA)
2422        if callable(rate):
2423            rain = lambda t: rate(t)/1000.0
2424        else:
2425            rain = rate/1000.0
2426
2427        if default_rate is not None:
2428            if callable(default_rate):
2429                default_rain = lambda t: default_rate(t)/1000.0
2430            else:
2431                default_rain = default_rate/1000.0
2432        else:
2433            default_rain = None
2434
2435           
2436           
2437        General_forcing.__init__(self,
2438                                 domain,
2439                                 'stage',
2440                                 rate=rain,
2441                                 center=center,
2442                                 radius=radius,
2443                                 polygon=polygon,
2444                                 default_rate=default_rain,
2445                                 verbose=verbose)
2446
2447
2448##
2449# @brief A class for inflow (rain and drain) forcing function.
2450# @note Inherits from General_forcing.
2451class Inflow(General_forcing):
2452    """Class Inflow - general 'rain and drain' forcing term.
2453
2454    Useful for implementing flows in and out of the domain.
2455
2456    Inflow(flow, center, radius, polygon)
2457
2458    domain
2459    rate [m^3/s]: Total flow rate over the specified area.
2460                  This parameter can be either a constant or a
2461                  function of time. Positive values indicate inflow,
2462                  negative values indicate outflow.
2463                  The specified flow will be divided by the area of
2464                  the inflow region and then applied to update stage.
2465    center [m]: Coordinates at center of flow point
2466    radius [m]: Size of circular area
2467    polygon:    Arbitrary polygon.
2468
2469    Either center, radius or polygon must be specified
2470
2471    Examples
2472
2473    # Constant drain at 0.003 m^3/s.
2474    # The outflow area is 0.07**2*pi=0.0154 m^2
2475    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
2476    #
2477    Inflow((0.7, 0.4), 0.07, -0.003)
2478
2479
2480    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
2481    # The inflow area is 0.03**2*pi = 0.00283 m^2
2482    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s
2483    # over the specified area
2484    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
2485
2486
2487    #------------------------------------------------------------------------
2488    # Setup specialised forcing terms
2489    #------------------------------------------------------------------------
2490    # This is the new element implemented by Ole to allow direct input
2491    # of Inflow in m^3/s
2492
2493    hydrograph = Inflow(center=(320, 300), radius=10,
2494                        rate=file_function('Q/QPMF_Rot_Sub13.tms'))
2495
2496    domain.forcing_terms.append(hydrograph)
2497    """
2498
2499    ##
2500    # @brief Create an instance of the class.
2501    # @param domain Domain of interest.
2502    # @param rate Total rain rate over the specified domain (mm/s).
2503    # @param center
2504    # @param radius
2505    # @param polygon Polygon  to restrict rainfall.
2506    # @param default_rate
2507    # @param verbose True if this instance is to be verbose.
2508    def __init__(self,
2509                 domain,
2510                 rate=0.0,
2511                 center=None,
2512                 radius=None,
2513                 polygon=None,
2514                 default_rate=None,
2515                 verbose=False):
2516        # Create object first to make area is available
2517        General_forcing.__init__(self,
2518                                 domain,
2519                                 'stage',
2520                                 rate=rate,
2521                                 center=center,
2522                                 radius=radius,
2523                                 polygon=polygon,
2524                                 default_rate=default_rate,
2525                                 verbose=verbose)
2526
2527    ##
2528    # @brief Update the instance rate.
2529    # @param t New rate object.
2530    def update_rate(self, t):
2531        """Virtual method allowing local modifications by writing an
2532        overriding version in descendant
2533
2534        This one converts m^3/s to m/s which can be added directly
2535        to 'stage' in ANUGA
2536        """
2537
2538        if callable(self.rate):
2539            _rate = self.rate(t)/self.exchange_area
2540        else:
2541            _rate = self.rate/self.exchange_area
2542
2543        return _rate
2544
2545
2546################################################################################
2547# Initialise module
2548################################################################################
2549
2550from anuga.utilities import compile
2551if compile.can_use_C_extension('shallow_water_ext.c'):
2552    # Underlying C implementations can be accessed
2553    from shallow_water_ext import rotate, assign_windfield_values
2554else:
2555    msg = 'C implementations could not be accessed by %s.\n ' % __file__
2556    msg += 'Make sure compile_all.py has been run as described in '
2557    msg += 'the ANUGA installation guide.'
2558    raise Exception, msg
2559
2560# Optimisation with psyco
2561from anuga.config import use_psyco
2562if use_psyco:
2563    try:
2564        import psyco
2565    except:
2566        import os
2567        if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
2568            pass
2569            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2570        else:
2571            msg = ('WARNING: psyco (speedup) could not be imported, '
2572                   'you may want to consider installing it')
2573            print msg
2574    else:
2575        psyco.bind(Domain.distribute_to_vertices_and_edges)
2576        psyco.bind(Domain.compute_fluxes)
2577
2578
2579if __name__ == "__main__":
2580    pass
Note: See TracBrowser for help on using the repository browser.