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

Last change on this file since 6654 was 6654, checked in by ole, 14 years ago

Added volumetric balance report for boundary flows and total volume.
Flows due to forcing terms have not yet been added.

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