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

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

Added AWI boundary as requested by Nils Goseberg (goseberg@…)
It does not yet have a unit test, but I have verified that all existing tests in ANUGA pass.

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