source: branches/Numeric_anuga_source/anuga/shallow_water/shallow_water_domain.py @ 7258

Last change on this file since 7258 was 7090, checked in by ole, 16 years ago

Added tests for volumetric conservation in the presence of inflow or rainfall.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 82.3 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: branches/Numeric_anuga_source/anuga/shallow_water/shallow_water_domain.py $
67
68Constraints: See GPL license in the user guide
69Version: 1.0 ($Revision: 7090 $)
70ModifiedBy:
71    $Author: ole $
72    $Date: 2009-05-26 07:41:49 +0000 (Tue, 26 May 2009) $
73
74"""
75
76# Subversion keywords:
77#
78# $LastChangedDate: 2009-05-26 07:41:49 +0000 (Tue, 26 May 2009) $
79# $LastChangedRevision: 7090 $
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
123import math
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        X = self.compute_boundary_flows() 
884        boundary_flows, total_boundary_inflow, total_boundary_outflow = X
885       
886        s = '---------------------------\n'       
887        s += 'Volumetric balance report:\n'
888        s += '--------------------------\n'
889        s += 'Total boundary inflow [m^3/s]: %.2e\n' % total_boundary_inflow
890        s += 'Total boundary outflow [m^3/s]: %.2e\n' % total_boundary_outflow       
891        s += 'Net boundary flow by tags [m^3/s]\n'
892        for tag in boundary_flows:
893            s += '    %s [m^3/s]: %.2e\n' % (tag, boundary_flows[tag])
894       
895        s += 'Total net boundary flow [m^3/s]: %.2e\n' % (total_boundary_inflow + total_boundary_outflow) 
896        s += 'Total volume in domain [m^3]: %.2e\n' % self.compute_total_volume()
897       
898        # The go through explicit forcing update and record the rate of change for stage and
899        # record into forcing_inflow and forcing_outflow. Finally compute integral
900        # of depth to obtain total volume of domain.
901       
902        # FIXME(Ole): This part is not yet done.               
903       
904        return s       
905           
906           
907           
908           
909               
910#=============== End of class Shallow Water Domain ===============================
911
912
913#-----------------
914# Flux computation
915#-----------------
916
917def compute_fluxes(domain):
918    """Compute all fluxes and the timestep suitable for all volumes
919    in domain.
920
921    Compute total flux for each conserved quantity using "flux_function"
922
923    Fluxes across each edge are scaled by edgelengths and summed up
924    Resulting flux is then scaled by area and stored in
925    explicit_update for each of the three conserved quantities
926    stage, xmomentum and ymomentum
927
928    The maximal allowable speed computed by the flux_function for each volume
929    is converted to a timestep that must not be exceeded. The minimum of
930    those is computed as the next overall timestep.
931
932    Post conditions:
933      domain.explicit_update is reset to computed flux values
934      domain.timestep is set to the largest step satisfying all volumes.
935   
936
937    This wrapper calls the underlying C version of compute fluxes
938    """
939
940    import sys
941
942    N = len(domain) # number_of_triangles
943
944    # Shortcuts
945    Stage = domain.quantities['stage']
946    Xmom = domain.quantities['xmomentum']
947    Ymom = domain.quantities['ymomentum']
948    Bed = domain.quantities['elevation']
949
950    timestep = float(sys.maxint)
951    from shallow_water_ext import\
952         compute_fluxes_ext_central as compute_fluxes_ext
953
954
955    flux_timestep = compute_fluxes_ext(timestep,
956                                       domain.epsilon,
957                                       domain.H0,
958                                       domain.g,
959                                       domain.neighbours,
960                                       domain.neighbour_edges,
961                                       domain.normals,
962                                       domain.edgelengths,
963                                       domain.radii,
964                                       domain.areas,
965                                       domain.tri_full_flag,
966                                       Stage.edge_values,
967                                       Xmom.edge_values,
968                                       Ymom.edge_values,
969                                       Bed.edge_values,
970                                       Stage.boundary_values,
971                                       Xmom.boundary_values,
972                                       Ymom.boundary_values,
973                                       Stage.explicit_update,
974                                       Xmom.explicit_update,
975                                       Ymom.explicit_update,
976                                       domain.already_computed_flux,
977                                       domain.max_speed,
978                                       int(domain.optimise_dry_cells))
979
980    domain.flux_timestep = flux_timestep
981
982
983
984#---------------------------------------
985# Module functions for gradient limiting
986#---------------------------------------
987
988
989# MH090605 The following method belongs to the shallow_water domain class
990# see comments in the corresponding method in shallow_water_ext.c
991def extrapolate_second_order_sw(domain):
992    """Wrapper calling C version of extrapolate_second_order_sw
993    """
994    import sys
995
996    N = len(domain) # number_of_triangles
997
998    # Shortcuts
999    Stage = domain.quantities['stage']
1000    Xmom = domain.quantities['xmomentum']
1001    Ymom = domain.quantities['ymomentum']
1002    Elevation = domain.quantities['elevation']
1003
1004    from shallow_water_ext import extrapolate_second_order_sw as extrapol2
1005    extrapol2(domain,
1006              domain.surrogate_neighbours,
1007              domain.number_of_boundaries,
1008              domain.centroid_coordinates,
1009              Stage.centroid_values,
1010              Xmom.centroid_values,
1011              Ymom.centroid_values,
1012              Elevation.centroid_values,
1013              domain.vertex_coordinates,
1014              Stage.vertex_values,
1015              Xmom.vertex_values,
1016              Ymom.vertex_values,
1017              Elevation.vertex_values,
1018              int(domain.optimise_dry_cells))
1019
1020
1021def distribute_using_vertex_limiter(domain):
1022    """Distribution from centroids to vertices specific to the
1023    shallow water wave
1024    equation.
1025
1026    It will ensure that h (w-z) is always non-negative even in the
1027    presence of steep bed-slopes by taking a weighted average between shallow
1028    and deep cases.
1029
1030    In addition, all conserved quantities get distributed as per either a
1031    constant (order==1) or a piecewise linear function (order==2).
1032
1033    FIXME: more explanation about removal of artificial variability etc
1034
1035    Precondition:
1036      All quantities defined at centroids and bed elevation defined at
1037      vertices.
1038
1039    Postcondition
1040      Conserved quantities defined at vertices
1041
1042    """
1043
1044   
1045
1046    # Remove very thin layers of water
1047    protect_against_infinitesimal_and_negative_heights(domain)
1048
1049    # Extrapolate all conserved quantities
1050    if domain.optimised_gradient_limiter:
1051        # MH090605 if second order,
1052        # perform the extrapolation and limiting on
1053        # all of the conserved quantities
1054
1055        if (domain._order_ == 1):
1056            for name in domain.conserved_quantities:
1057                Q = domain.quantities[name]
1058                Q.extrapolate_first_order()
1059        elif domain._order_ == 2:
1060            domain.extrapolate_second_order_sw()
1061        else:
1062            raise 'Unknown order'
1063    else:
1064        # Old code:
1065        for name in domain.conserved_quantities:
1066            Q = domain.quantities[name]
1067
1068            if domain._order_ == 1:
1069                Q.extrapolate_first_order()
1070            elif domain._order_ == 2:
1071                Q.extrapolate_second_order_and_limit_by_vertex()
1072            else:
1073                raise 'Unknown order'
1074
1075
1076    # Take bed elevation into account when water heights are small
1077    balance_deep_and_shallow(domain)
1078
1079    # Compute edge values by interpolation
1080    for name in domain.conserved_quantities:
1081        Q = domain.quantities[name]
1082        Q.interpolate_from_vertices_to_edges()
1083
1084
1085
1086def distribute_using_edge_limiter(domain):
1087    """Distribution from centroids to edges specific to the
1088    shallow water wave
1089    equation.
1090
1091    It will ensure that h (w-z) is always non-negative even in the
1092    presence of steep bed-slopes by taking a weighted average between shallow
1093    and deep cases.
1094
1095    In addition, all conserved quantities get distributed as per either a
1096    constant (order==1) or a piecewise linear function (order==2).
1097
1098
1099    Precondition:
1100      All quantities defined at centroids and bed elevation defined at
1101      vertices.
1102
1103    Postcondition
1104      Conserved quantities defined at vertices
1105
1106    """
1107
1108    # Remove very thin layers of water
1109    protect_against_infinitesimal_and_negative_heights(domain)
1110
1111
1112    for name in domain.conserved_quantities:
1113        Q = domain.quantities[name]
1114        if domain._order_ == 1:
1115            Q.extrapolate_first_order()
1116        elif domain._order_ == 2:
1117            Q.extrapolate_second_order_and_limit_by_edge()
1118        else:
1119            raise 'Unknown order'
1120
1121    balance_deep_and_shallow(domain)
1122
1123    # Compute edge values by interpolation
1124    for name in domain.conserved_quantities:
1125        Q = domain.quantities[name]
1126        Q.interpolate_from_vertices_to_edges()
1127
1128
1129def protect_against_infinitesimal_and_negative_heights(domain):
1130    """Protect against infinitesimal heights and associated high velocities
1131    """
1132
1133    # Shortcuts
1134    wc = domain.quantities['stage'].centroid_values
1135    zc = domain.quantities['elevation'].centroid_values
1136    xmomc = domain.quantities['xmomentum'].centroid_values
1137    ymomc = domain.quantities['ymomentum'].centroid_values
1138
1139    from shallow_water_ext import protect
1140
1141    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
1142            domain.epsilon, wc, zc, xmomc, ymomc)
1143
1144
1145
1146def balance_deep_and_shallow(domain):
1147    """Compute linear combination between stage as computed by
1148    gradient-limiters limiting using w, and stage computed by
1149    gradient-limiters limiting using h (h-limiter).
1150    The former takes precedence when heights are large compared to the
1151    bed slope while the latter takes precedence when heights are
1152    relatively small.  Anything in between is computed as a balanced
1153    linear combination in order to avoid numerical disturbances which
1154    would otherwise appear as a result of hard switching between
1155    modes.
1156
1157    Wrapper for C implementation
1158    """
1159
1160    from shallow_water_ext import balance_deep_and_shallow as balance_deep_and_shallow_c
1161
1162
1163    # Shortcuts
1164    wc = domain.quantities['stage'].centroid_values
1165    zc = domain.quantities['elevation'].centroid_values
1166
1167    wv = domain.quantities['stage'].vertex_values
1168    zv = domain.quantities['elevation'].vertex_values
1169
1170    # Momentums at centroids
1171    xmomc = domain.quantities['xmomentum'].centroid_values
1172    ymomc = domain.quantities['ymomentum'].centroid_values
1173
1174    # Momentums at vertices
1175    xmomv = domain.quantities['xmomentum'].vertex_values
1176    ymomv = domain.quantities['ymomentum'].vertex_values
1177
1178    balance_deep_and_shallow_c(domain,
1179                               wc, zc, wv, zv, wc, 
1180                               xmomc, ymomc, xmomv, ymomv)
1181
1182
1183
1184
1185#------------------------------------------------------------------
1186# Boundary conditions - specific to the shallow water wave equation
1187#------------------------------------------------------------------
1188class Reflective_boundary(Boundary):
1189    """Reflective boundary returns same conserved quantities as
1190    those present in its neighbour volume but reflected.
1191
1192    This class is specific to the shallow water equation as it
1193    works with the momentum quantities assumed to be the second
1194    and third conserved quantities.
1195    """
1196
1197    def __init__(self, domain = None):
1198        Boundary.__init__(self)
1199
1200        if domain is None:
1201            msg = 'Domain must be specified for reflective boundary'
1202            raise msg
1203
1204        # Handy shorthands
1205        self.stage   = domain.quantities['stage'].edge_values
1206        self.xmom    = domain.quantities['xmomentum'].edge_values
1207        self.ymom    = domain.quantities['ymomentum'].edge_values
1208        self.normals = domain.normals
1209
1210        self.conserved_quantities = num.zeros(3, num.Float)
1211
1212    def __repr__(self):
1213        return 'Reflective_boundary'
1214
1215
1216    def evaluate(self, vol_id, edge_id):
1217        """Reflective boundaries reverses the outward momentum
1218        of the volume they serve.
1219        """
1220
1221        q = self.conserved_quantities
1222        q[0] = self.stage[vol_id, edge_id]
1223        q[1] = self.xmom[vol_id, edge_id]
1224        q[2] = self.ymom[vol_id, edge_id]
1225
1226        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
1227
1228
1229        r = rotate(q, normal, direction = 1)
1230        r[1] = -r[1]
1231        q = rotate(r, normal, direction = -1)
1232
1233        return q
1234
1235
1236
1237
1238class Transmissive_momentum_set_stage_boundary(Boundary):
1239    """Returns same momentum conserved quantities as
1240    those present in its neighbour volume.
1241    Sets stage by specifying a function f of time which may either be a
1242    vector function or a scalar function
1243
1244    Example:
1245
1246    def waveform(t):
1247        return sea_level + normalized_amplitude/cosh(t-25)**2
1248
1249    Bts = Transmissive_momentum_set_stage_boundary(domain, waveform)
1250   
1251
1252    Underlying domain must be specified when boundary is instantiated
1253    """
1254
1255    def __init__(self, domain = None, function=None):
1256        Boundary.__init__(self)
1257
1258        if domain is None:
1259            msg = 'Domain must be specified for this type boundary'
1260            raise msg
1261
1262        if function is None:
1263            msg = 'Function must be specified for this type boundary'
1264            raise msg
1265
1266        self.domain   = domain
1267        self.function = function
1268
1269    def __repr__(self):
1270        return 'Transmissive_momentum_set_stage_boundary(%s)' %self.domain
1271
1272    def evaluate(self, vol_id, edge_id):
1273        """Transmissive momentum set stage boundaries return the edge momentum
1274        values of the volume they serve.
1275        """
1276
1277        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
1278
1279
1280        t = self.domain.get_time()
1281
1282        if hasattr(self.function, 'time'):
1283            # Roll boundary over if time exceeds           
1284            while t > self.function.time[-1]:
1285                msg = 'WARNING: domain time %.2f has exceeded' %t
1286                msg += 'time provided in '
1287                msg += 'transmissive_momentum_set_stage boundary object.\n'
1288                msg += 'I will continue, reusing the object from t==0'
1289                print msg
1290                t -= self.function.time[-1]
1291
1292
1293        value = self.function(t)
1294        try:
1295            x = float(value)
1296        except:
1297            x = float(value[0])
1298           
1299        q[0] = x
1300        return q
1301
1302
1303        # FIXME: Consider this (taken from File_boundary) to allow
1304        # spatial variation
1305        # if vol_id is not None and edge_id is not None:
1306        #     i = self.boundary_indices[ vol_id, edge_id ]
1307        #     return self.F(t, point_id = i)
1308        # else:
1309        #     return self.F(t)
1310
1311
1312# Backward compatibility       
1313# FIXME(Ole): Deprecate
1314class Transmissive_Momentum_Set_Stage_boundary(Transmissive_momentum_set_stage_boundary):
1315    pass
1316
1317     
1318
1319class Transmissive_stage_zero_momentum_boundary(Boundary):
1320    """Return same stage as those present in its neighbour volume. Set momentum to zero.
1321
1322    Underlying domain must be specified when boundary is instantiated
1323    """
1324
1325    def __init__(self, domain=None):
1326        Boundary.__init__(self)
1327
1328        if domain is None:
1329            msg = 'Domain must be specified for '
1330            msg += 'Transmissive_stage_zero_momentum boundary'
1331            raise Exception, msg
1332
1333        self.domain = domain
1334
1335    def __repr__(self):
1336        return 'Transmissive_stage_zero_momentum_boundary(%s)' %self.domain
1337
1338    def evaluate(self, vol_id, edge_id):
1339        """Transmissive boundaries return the edge values
1340        of the volume they serve.
1341        """
1342
1343        q = self.domain.get_conserved_quantities(vol_id, edge=edge_id)
1344       
1345        q[1] = q[2] = 0.0
1346        return q
1347
1348
1349       
1350class Dirichlet_discharge_boundary(Boundary):
1351    """
1352    Sets stage (stage0)
1353    Sets momentum (wh0) in the inward normal direction.
1354
1355    Underlying domain must be specified when boundary is instantiated
1356    """
1357
1358    def __init__(self, domain=None, stage0=None, wh0=None):
1359        Boundary.__init__(self)
1360
1361        if domain is None:
1362            msg = 'Domain must be specified for this type boundary'
1363            raise msg
1364
1365        if stage0 is None:
1366            raise 'set stage'
1367
1368        if wh0 is None:
1369            wh0 = 0.0
1370
1371        self.domain   = domain
1372        self.stage0  = stage0
1373        self.wh0 = wh0
1374
1375    def __repr__(self):
1376        return 'Dirichlet_Discharge_boundary(%s)' %self.domain
1377
1378    def evaluate(self, vol_id, edge_id):
1379        """Set discharge in the (inward) normal direction
1380        """
1381
1382        normal = self.domain.get_normal(vol_id,edge_id)
1383        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1384        return q
1385
1386
1387        # FIXME: Consider this (taken from File_boundary) to allow
1388        # spatial variation
1389        # if vol_id is not None and edge_id is not None:
1390        #     i = self.boundary_indices[ vol_id, edge_id ]
1391        #     return self.F(t, point_id = i)
1392        # else:
1393        #     return self.F(t)
1394
1395
1396       
1397# Backward compatibility       
1398# FIXME(Ole): Deprecate
1399class Dirichlet_Discharge_boundary(Dirichlet_discharge_boundary):
1400    pass
1401                                                   
1402   
1403
1404
1405   
1406class Inflow_boundary(Boundary):
1407    """Apply given flow in m^3/s to boundary segment.
1408    Depth and momentum is derived using Manning's formula.
1409
1410    Underlying domain must be specified when boundary is instantiated
1411    """
1412   
1413    # FIXME (Ole): This is work in progress and definitely not finished.
1414    # The associated test has been disabled
1415
1416    def __init__(self, domain=None, rate=0.0):
1417        Boundary.__init__(self)
1418
1419        if domain is None:
1420            msg = 'Domain must be specified for '
1421            msg += 'Inflow boundary'
1422            raise Exception, msg
1423
1424        self.domain = domain
1425       
1426        # FIXME(Ole): Allow rate to be time dependent as well
1427        self.rate = rate
1428        self.tag = None # Placeholder for tag associated with this object.
1429
1430    def __repr__(self):
1431        return 'Inflow_boundary(%s)' %self.domain
1432
1433    def evaluate(self, vol_id, edge_id):
1434        """Apply inflow rate at each edge of this boundary
1435        """
1436       
1437        # First find all segments having the same tag is vol_id, edge_id
1438        # This will be done the first time evaluate is called.
1439        if self.tag is None:
1440            boundary = self.domain.boundary
1441            self.tag = boundary[(vol_id, edge_id)]       
1442           
1443            # Find total length of boundary with this tag
1444            length = 0.0
1445            for v_id, e_id in boundary:
1446                if self.tag == boundary[(v_id, e_id)]:
1447                    length += self.domain.mesh.get_edgelength(v_id, e_id)           
1448
1449            self.length = length
1450            self.average_momentum = self.rate/length
1451           
1452           
1453        # Average momentum has now been established across this boundary
1454        # Compute momentum in the inward normal direction
1455       
1456        inward_normal = -self.domain.mesh.get_normal(vol_id, edge_id)       
1457        xmomentum, ymomentum = self.average_momentum * inward_normal
1458           
1459        # Compute depth based on Manning's formula v = 1/n h^{2/3} sqrt(S)
1460        # Where v is velocity, n is manning's coefficient, h is depth and S is the slope into the domain.
1461        # Let mu be the momentum (vh), then this equation becomes: mu = 1/n h^{5/3} sqrt(S)
1462        # from which we can isolate depth to get
1463        # h = (mu n/sqrt(S) )^{3/5}
1464       
1465        slope = 0 # get gradient for this triangle dot normal
1466       
1467        # get manning coef from this triangle
1468        friction = self.domain.get_quantity('friction').get_values(location='edges', 
1469                                                                   indices=[vol_id])[0]
1470        mannings_n = friction[edge_id]
1471
1472        if slope > epsilon and mannings_n > epsilon:
1473            depth = pow(self.average_momentum * mannings_n/math.sqrt(slope), 3.0/5) 
1474        else:
1475            depth = 1.0
1476           
1477        # Elevation on this edge   
1478       
1479        z = self.domain.get_quantity('elevation').get_values(location='edges', 
1480                                                             indices=[vol_id])[0]
1481        elevation = z[edge_id]
1482           
1483        # Assign conserved quantities and return
1484        q = num.array([elevation + depth, xmomentum, ymomentum], num.Float)
1485        return q
1486
1487
1488       
1489   
1490           
1491       
1492class Field_boundary(Boundary):
1493    """Set boundary from given field represented in an sww file containing values
1494    for stage, xmomentum and ymomentum.
1495    Optionally, the user can specify mean_stage to offset the stage provided in the
1496    sww file.
1497
1498    This function is a thin wrapper around the generic File_boundary. The
1499    difference between the file_boundary and field_boundary is only that the
1500    field_boundary will allow you to change the level of the stage height when
1501    you read in the boundary condition. This is very useful when running
1502    different tide heights in the same area as you need only to convert one
1503    boundary condition to a SWW file, ideally for tide height of 0 m
1504    (saving disk space). Then you can use field_boundary to read this SWW file
1505    and change the stage height (tide) on the fly depending on the scenario.
1506   
1507    """
1508
1509
1510    def __init__(self, filename, domain,
1511                 mean_stage=0.0,
1512                 time_thinning=1,
1513                 time_limit=None,
1514                 boundary_polygon=None,   
1515                 default_boundary=None,                 
1516                 use_cache=False,
1517                 verbose=False):
1518        """Constructor
1519
1520        filename: Name of sww file
1521        domain: pointer to shallow water domain for which the boundary applies
1522        mean_stage: The mean water level which will be added to stage derived
1523                    from the boundary condition
1524        time_thinning: Will set how many time steps from the sww file read in
1525                       will be interpolated to the boundary. For example if
1526                       the sww file has 1 second time steps and is 24 hours
1527                       in length it has 86400 time steps. If you set
1528                       time_thinning to 1 it will read all these steps.
1529                       If you set it to 100 it will read every 100th step eg
1530                       only 864 step. This parameter is very useful to increase
1531                       the speed of a model run that you are setting up
1532                       and testing.
1533                       
1534        default_boundary: Must be either None or an instance of a
1535                          class descending from class Boundary.
1536                          This will be used in case model time exceeds
1537                          that available in the underlying data.
1538                          Note that mean_stage will also be added to this.
1539                                               
1540        use_cache:
1541        verbose:
1542       
1543        """
1544
1545        # Create generic file_boundary object
1546        self.file_boundary = File_boundary(filename, domain,
1547                                           time_thinning=time_thinning,
1548                                           time_limit=time_limit,
1549                                           boundary_polygon=boundary_polygon,
1550                                           default_boundary=default_boundary,
1551                                           use_cache=use_cache,
1552                                           verbose=verbose)
1553
1554       
1555        # Record information from File_boundary
1556        self.F = self.file_boundary.F
1557        self.domain = self.file_boundary.domain
1558       
1559        # Record mean stage
1560        self.mean_stage = mean_stage
1561
1562
1563    def __repr__(self):
1564        return 'Field boundary'
1565
1566
1567    def evaluate(self, vol_id=None, edge_id=None):
1568        """Return linearly interpolated values based on domain.time
1569
1570        vol_id and edge_id are ignored
1571        """
1572       
1573        # Evaluate file boundary
1574        q = self.file_boundary.evaluate(vol_id, edge_id)
1575
1576        # Adjust stage
1577        for j, name in enumerate(self.domain.conserved_quantities):
1578            if name == 'stage':
1579                q[j] += self.mean_stage
1580        return q
1581
1582   
1583
1584#-----------------------
1585# Standard forcing terms
1586#-----------------------
1587
1588def gravity(domain):
1589    """Apply gravitational pull in the presence of bed slope
1590    Wrapper calls underlying C implementation
1591    """
1592
1593    xmom = domain.quantities['xmomentum'].explicit_update
1594    ymom = domain.quantities['ymomentum'].explicit_update
1595
1596    stage = domain.quantities['stage']
1597    elevation = domain.quantities['elevation']
1598
1599    h = stage.centroid_values - elevation.centroid_values
1600    z = elevation.vertex_values
1601
1602    x = domain.get_vertex_coordinates()
1603    g = domain.g
1604   
1605
1606    from shallow_water_ext import gravity as gravity_c
1607    gravity_c(g, h, z, x, xmom, ymom) #, 1.0e-6)
1608
1609
1610
1611def manning_friction_implicit(domain):
1612    """Apply (Manning) friction to water momentum   
1613    Wrapper for c version
1614    """
1615
1616
1617    #print 'Implicit friction'
1618
1619    xmom = domain.quantities['xmomentum']
1620    ymom = domain.quantities['ymomentum']
1621
1622    w = domain.quantities['stage'].centroid_values
1623    z = domain.quantities['elevation'].centroid_values
1624
1625    uh = xmom.centroid_values
1626    vh = ymom.centroid_values
1627    eta = domain.quantities['friction'].centroid_values
1628
1629    xmom_update = xmom.semi_implicit_update
1630    ymom_update = ymom.semi_implicit_update
1631
1632    N = len(domain)
1633    eps = domain.minimum_allowed_height
1634    g = domain.g
1635
1636    from shallow_water_ext import manning_friction as manning_friction_c
1637    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1638
1639
1640def manning_friction_explicit(domain):
1641    """Apply (Manning) friction to water momentum   
1642    Wrapper for c version
1643    """
1644
1645    # print 'Explicit friction'
1646
1647    xmom = domain.quantities['xmomentum']
1648    ymom = domain.quantities['ymomentum']
1649
1650    w = domain.quantities['stage'].centroid_values
1651    z = domain.quantities['elevation'].centroid_values
1652
1653    uh = xmom.centroid_values
1654    vh = ymom.centroid_values
1655    eta = domain.quantities['friction'].centroid_values
1656
1657    xmom_update = xmom.explicit_update
1658    ymom_update = ymom.explicit_update
1659
1660    N = len(domain)
1661    eps = domain.minimum_allowed_height
1662    g = domain.g
1663
1664    from shallow_water_ext import manning_friction as manning_friction_c
1665    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1666
1667
1668# FIXME (Ole): This was implemented for use with one of the analytical solutions (Sampson?)
1669# Is it still needed (30 Oct 2007)?
1670def linear_friction(domain):
1671    """Apply linear friction to water momentum
1672
1673    Assumes quantity: 'linear_friction' to be present
1674    """
1675
1676    from math import sqrt
1677
1678    w = domain.quantities['stage'].centroid_values
1679    z = domain.quantities['elevation'].centroid_values
1680    h = w-z
1681
1682    uh = domain.quantities['xmomentum'].centroid_values
1683    vh = domain.quantities['ymomentum'].centroid_values
1684    tau = domain.quantities['linear_friction'].centroid_values
1685
1686    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1687    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1688
1689    N = len(domain) # number_of_triangles
1690    eps = domain.minimum_allowed_height
1691    g = domain.g #Not necessary? Why was this added?
1692
1693    for k in range(N):
1694        if tau[k] >= eps:
1695            if h[k] >= eps:
1696                S = -tau[k]/h[k]
1697
1698                #Update momentum
1699                xmom_update[k] += S*uh[k]
1700                ymom_update[k] += S*vh[k]
1701
1702
1703
1704
1705def depth_dependent_friction(domain, default_friction,
1706                             surface_roughness_data,
1707                             verbose=False):
1708    """Returns an array of friction values for each wet element adjusted for depth.
1709
1710    Inputs:
1711        domain - computational domain object
1712        default_friction - depth independent bottom friction
1713        surface_roughness_data - N x 5 array of n0, d1, n1, d2, n2 values for each
1714        friction region.
1715
1716    Outputs:
1717        wet_friction - Array that can be used directly to update friction as follows:
1718                       domain.set_quantity('friction', wet_friction)
1719
1720       
1721       
1722    """
1723
1724    import Numeric as num
1725   
1726    # Create a temp array to store updated depth dependent friction for wet elements
1727    # EHR this is outwardly inneficient but not obvious how to avoid recreating each call??????
1728    N=len(domain)
1729    wet_friction    = num.zeros(N, num.Float)
1730    wet_friction[:] = default_n0   # Initially assign default_n0 to all array so sure have no zeros values
1731   
1732   
1733    depth = domain.create_quantity_from_expression('stage - elevation')  # create depth instance for this timestep
1734    # Recompute depth as vector 
1735    d = depth.get_values(location='centroids')
1736 
1737    # rebuild the 'friction' values adjusted for depth at this instant
1738    for i in domain.get_wet_elements():                                  # loop for each wet element in domain
1739       
1740        # Get roughness data for each element
1741        n0 = float(surface_roughness_data[i,0])
1742        d1 = float(surface_roughness_data[i,1])
1743        n1 = float(surface_roughness_data[i,2])
1744        d2 = float(surface_roughness_data[i,3])
1745        n2 = float(surface_roughness_data[i,4])
1746       
1747       
1748        # Recompute friction values from depth for this element
1749               
1750        if d[i]   <= d1: 
1751            depth_dependent_friction = n1
1752        elif d[i] >= d2:
1753            depth_dependent_friction = n2
1754        else:
1755            depth_dependent_friction = n1+((n2-n1)/(d2-d1))*(d[i]-d1)
1756           
1757        # check sanity of result
1758        if (depth_dependent_friction  < 0.010 or depth_dependent_friction > 9999.0) :
1759            print model_data.basename+' >>>> WARNING: computed depth_dependent friction out of range ddf,n1,n2 ', depth_dependent_friction, n1,n2
1760       
1761        # update depth dependent friction  for that wet element
1762        wet_friction[i] = depth_dependent_friction
1763       
1764    # EHR add code to show range of 'friction across domain at this instant as sanity check?????????
1765   
1766    if verbose :
1767        nvals=domain.get_quantity('friction').get_values(location='centroids')        # return array of domain nvals
1768        n_min=min(nvals)
1769        n_max=max(nvals)
1770       
1771        print "         ++++ calculate_depth_dependent_friction  - Updated friction - range  %7.3f to %7.3f" %(n_min,n_max)
1772   
1773    return wet_friction
1774
1775
1776
1777
1778
1779
1780#---------------------------------
1781# Experimental auxiliary functions
1782#---------------------------------
1783def check_forcefield(f):
1784    """Check that f is either
1785    1: a callable object f(t,x,y), where x and y are vectors
1786       and that it returns an array or a list of same length
1787       as x and y
1788    2: a scalar
1789    """
1790
1791    if callable(f):
1792        N = 3
1793        x = num.ones(3, num.Float)
1794        y = num.ones(3, num.Float)
1795        try:
1796            q = f(1.0, x=x, y=y)
1797        except Exception, e:
1798            msg = 'Function %s could not be executed:\n%s' %(f, e)
1799            # FIXME: Reconsider this semantics
1800            raise msg
1801
1802        try:
1803            q = num.array(q, num.Float)
1804        except:
1805            msg = 'Return value from vector function %s could ' %f
1806            msg += 'not be converted into a Numeric array of floats.\n'
1807            msg += 'Specified function should return either list or array.'
1808            raise msg
1809
1810        # Is this really what we want?
1811        msg = 'Return vector from function %s ' %f
1812        msg += 'must have same lenght as input vectors'
1813        assert len(q) == N, msg
1814
1815    else:
1816        try:
1817            f = float(f)
1818        except:
1819            msg = 'Force field %s must be either a scalar' %f
1820            msg += ' or a vector function'
1821            raise Exception(msg)
1822    return f
1823
1824
1825class Wind_stress:
1826    """Apply wind stress to water momentum in terms of
1827    wind speed [m/s] and wind direction [degrees]
1828    """
1829
1830    def __init__(self, *args, **kwargs):
1831        """Initialise windfield from wind speed s [m/s]
1832        and wind direction phi [degrees]
1833
1834        Inputs v and phi can be either scalars or Python functions, e.g.
1835
1836        W = Wind_stress(10, 178)
1837
1838        #FIXME - 'normal' degrees are assumed for now, i.e. the
1839        vector (1,0) has zero degrees.
1840        We may need to convert from 'compass' degrees later on and also
1841        map from True north to grid north.
1842
1843        Arguments can also be Python functions of t,x,y as in
1844
1845        def speed(t,x,y):
1846            ...
1847            return s
1848
1849        def angle(t,x,y):
1850            ...
1851            return phi
1852
1853        where x and y are vectors.
1854
1855        and then pass the functions in
1856
1857        W = Wind_stress(speed, angle)
1858
1859        The instantiated object W can be appended to the list of
1860        forcing_terms as in
1861
1862        Alternatively, one vector valued function for (speed, angle)
1863        can be applied, providing both quantities simultaneously.
1864        As in
1865        W = Wind_stress(F), where returns (speed, angle) for each t.
1866
1867        domain.forcing_terms.append(W)
1868        """
1869
1870        from anuga.config import rho_a, rho_w, eta_w
1871
1872        if len(args) == 2:
1873            s = args[0]
1874            phi = args[1]
1875        elif len(args) == 1:
1876            # Assume vector function returning (s, phi)(t,x,y)
1877            vector_function = args[0]
1878            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1879            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1880        else:
1881           # Assume info is in 2 keyword arguments
1882
1883           if len(kwargs) == 2:
1884               s = kwargs['s']
1885               phi = kwargs['phi']
1886           else:
1887               raise 'Assumes two keyword arguments: s=..., phi=....'
1888
1889        self.speed = check_forcefield(s)
1890        self.phi = check_forcefield(phi)
1891
1892        self.const = eta_w*rho_a/rho_w
1893
1894
1895    def __call__(self, domain):
1896        """Evaluate windfield based on values found in domain
1897        """
1898
1899        from math import pi, cos, sin, sqrt
1900
1901        xmom_update = domain.quantities['xmomentum'].explicit_update
1902        ymom_update = domain.quantities['ymomentum'].explicit_update
1903
1904        N = len(domain) # number_of_triangles
1905        t = domain.time
1906
1907        if callable(self.speed):
1908            xc = domain.get_centroid_coordinates()
1909            s_vec = self.speed(t, xc[:,0], xc[:,1])
1910        else:
1911            # Assume s is a scalar
1912
1913            try:
1914                s_vec = self.speed * num.ones(N, num.Float)
1915            except:
1916                msg = 'Speed must be either callable or a scalar: %s' %self.s
1917                raise msg
1918
1919
1920        if callable(self.phi):
1921            xc = domain.get_centroid_coordinates()
1922            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1923        else:
1924            # Assume phi is a scalar
1925
1926            try:
1927                phi_vec = self.phi * num.ones(N, num.Float)
1928            except:
1929                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1930                raise msg
1931
1932        assign_windfield_values(xmom_update, ymom_update,
1933                                s_vec, phi_vec, self.const)
1934
1935
1936def assign_windfield_values(xmom_update, ymom_update,
1937                            s_vec, phi_vec, const):
1938    """Python version of assigning wind field to update vectors.
1939    A c version also exists (for speed)
1940    """
1941    from math import pi, cos, sin, sqrt
1942
1943    N = len(s_vec)
1944    for k in range(N):
1945        s = s_vec[k]
1946        phi = phi_vec[k]
1947
1948        # Convert to radians
1949        phi = phi*pi/180
1950
1951        # Compute velocity vector (u, v)
1952        u = s*cos(phi)
1953        v = s*sin(phi)
1954
1955        # Compute wind stress
1956        S = const * sqrt(u**2 + v**2)
1957        xmom_update[k] += S*u
1958        ymom_update[k] += S*v
1959
1960
1961
1962
1963
1964class General_forcing:
1965    """General explicit forcing term for update of quantity
1966   
1967    This is used by Inflow and Rainfall for instance
1968   
1969
1970    General_forcing(quantity_name, rate, center, radius, polygon)
1971
1972    domain:     ANUGA computational domain
1973    quantity_name: Name of quantity to update.
1974                   It must be a known conserved quantity.
1975                   
1976    rate [?/s]: Total rate of change over the specified area. 
1977                This parameter can be either a constant or a
1978                function of time. Positive values indicate increases,
1979                negative values indicate decreases.
1980                Rate can be None at initialisation but must be specified
1981                before forcing term is applied (i.e. simulation has started).
1982
1983    center [m]: Coordinates at center of flow point
1984    radius [m]: Size of circular area
1985    polygon:    Arbitrary polygon
1986    default_rate: Rate to be used if rate fails (e.g. if model time exceeds its data)
1987                  Admissible types: None, constant number or function of t
1988
1989
1990    Either center, radius or polygon can be specified but not both.
1991    If neither are specified the entire domain gets updated.
1992    All coordinates to be specified in absolute UTM coordinates (x, y) assuming the zone of domain.   
1993   
1994    Inflow or Rainfall for examples of use
1995    """
1996
1997
1998    # FIXME (AnyOne) : Add various methods to allow spatial variations
1999
2000    def __init__(self,
2001                 domain,
2002                 quantity_name,
2003                 rate=0.0,
2004                 center=None, radius=None,
2005                 polygon=None,
2006                 default_rate=None,
2007                 verbose=False):
2008                     
2009        if center is None:
2010            msg = 'I got radius but no center.'       
2011            assert radius is None, msg
2012           
2013        if radius is None:
2014            msg += 'I got center but no radius.'       
2015            assert center is None, msg
2016           
2017           
2018                     
2019        from math import pi, cos, sin
2020
2021        self.domain = domain
2022        self.quantity_name = quantity_name
2023        self.rate = rate
2024        self.center = ensure_numeric(center)
2025        self.radius = radius
2026        self.polygon = polygon       
2027        self.verbose = verbose
2028        self.value = 0.0 # Can be used to remember value at
2029                         # previous timestep in order to obtain rate
2030
2031        # Get boundary (in absolute coordinates)
2032        bounding_polygon = domain.get_boundary_polygon()
2033
2034
2035        # Update area if applicable
2036        if center is not None and radius is not None:
2037            assert len(center) == 2
2038            msg = 'Polygon cannot be specified when center and radius are'
2039            assert polygon is None, msg
2040
2041
2042            # Check that circle center lies within the mesh.
2043            msg = 'Center %s specified for forcing term did not' %(str(center))
2044            msg += 'fall within the domain boundary.'
2045            assert is_inside_polygon(center, bounding_polygon), msg
2046
2047            # Check that circle periphery lies within the mesh.
2048            N = 100
2049            periphery_points = []
2050            for i in range(N):
2051
2052                theta = 2*pi*i/100
2053               
2054                x = center[0] + radius*cos(theta)
2055                y = center[1] + radius*sin(theta)
2056
2057                periphery_points.append([x,y])
2058
2059
2060            for point in periphery_points:
2061                msg = 'Point %s on periphery for forcing term' %(str(point))
2062                msg += ' did not fall within the domain boundary.'
2063                assert is_inside_polygon(point, bounding_polygon), msg
2064
2065       
2066        if polygon is not None:
2067
2068            # Check that polygon lies within the mesh.
2069            for point in self.polygon:
2070                msg = 'Point %s in polygon for forcing term' %(point)
2071                msg += ' did not fall within the domain boundary.'
2072                assert is_inside_polygon(point, bounding_polygon), msg
2073               
2074                                 
2075
2076        # Pointer to update vector
2077        self.update = domain.quantities[self.quantity_name].explicit_update
2078
2079        # Determine indices in flow area
2080        N = len(domain)   
2081        points = domain.get_centroid_coordinates(absolute=True)
2082
2083        # Calculate indices in exchange area for this forcing term
2084        self.exchange_indices = None
2085        if self.center is not None and self.radius is not None:
2086            # Inlet is circular
2087           
2088            inlet_region = 'center=%s, radius=%s' %(self.center, self.radius) 
2089           
2090            self.exchange_indices = []
2091            for k in range(N):
2092                x, y = points[k,:] # Centroid
2093               
2094                c = self.center
2095                if ((x-c[0])**2+(y-c[1])**2) < self.radius**2:
2096                    self.exchange_indices.append(k)
2097                   
2098        if self.polygon is not None:                   
2099            # Inlet is polygon
2100           
2101            inlet_region = 'polygon=%s' % (self.polygon) 
2102            self.exchange_indices = inside_polygon(points, self.polygon)
2103           
2104           
2105           
2106        if self.exchange_indices is None:
2107            self.exchange_area = polygon_area(bounding_polygon)
2108        else:   
2109            if len(self.exchange_indices) == 0:
2110                msg = 'No triangles have been identified in '
2111                msg += 'specified region: %s' %inlet_region
2112                raise Exception, msg
2113
2114            # Compute exchange area as the sum of areas of triangles identified
2115            # by circle or polygon
2116            self.exchange_area = 0.0
2117            for i in self.exchange_indices:
2118                self.exchange_area += domain.areas[i]
2119           
2120
2121        msg = 'Exchange area in forcing term'
2122        msg += ' has area = %f' %self.exchange_area
2123        assert self.exchange_area > 0.0           
2124           
2125               
2126
2127           
2128        # Check and store default_rate
2129        msg = 'Keyword argument default_rate must be either None ' 
2130        msg += 'or a function of time.\n I got %s' %(str(default_rate)) 
2131        assert default_rate is None or \
2132               type(default_rate) in [IntType, FloatType] or \
2133               callable(default_rate), msg
2134       
2135        if default_rate is not None:
2136
2137            # If it is a constant, make it a function
2138            if not callable(default_rate):
2139                tmp = default_rate
2140                default_rate = lambda t: tmp
2141
2142           
2143            # Check that default_rate is a function of one argument
2144            try:
2145                default_rate(0.0)
2146            except:
2147                raise Exception, msg
2148
2149        self.default_rate = default_rate
2150        self.default_rate_invoked = False    # Flag       
2151       
2152
2153    def __call__(self, domain):
2154        """Apply inflow function at time specified in domain and update stage
2155        """
2156
2157        # Call virtual method allowing local modifications
2158
2159        t = domain.get_time()
2160        try:
2161            rate = self.update_rate(t)
2162        except Modeltime_too_early, e:
2163            raise Modeltime_too_early, e
2164        except Modeltime_too_late, e:
2165            if self.default_rate is None:
2166                raise Exception, e # Reraise exception
2167            else:
2168                # Pass control to default rate function
2169                rate = self.default_rate(t)
2170               
2171                if self.default_rate_invoked is False:
2172                    # Issue warning the first time
2173                    msg = '%s' %str(e)
2174                    msg += 'Instead I will use the default rate: %s\n'\
2175                        %str(self.default_rate) 
2176                    msg += 'Note: Further warnings will be supressed'
2177                    warn(msg)
2178                   
2179                    # FIXME (Ole): Replace this crude flag with
2180                    # Python's ability to print warnings only once.
2181                    # See http://docs.python.org/lib/warning-filter.html
2182                    self.default_rate_invoked = True
2183                   
2184
2185           
2186               
2187
2188        if rate is None:
2189            msg = 'Attribute rate must be specified in General_forcing'
2190            msg += ' or its descendants before attempting to call it'
2191            raise Exception, msg
2192       
2193
2194        # Now rate is a number
2195        if self.verbose is True:
2196            print 'Rate of %s at time = %.2f = %f' %(self.quantity_name,
2197                                                     domain.get_time(),
2198                                                     rate)
2199
2200
2201        if self.exchange_indices is None:
2202            self.update[:] += rate
2203        else:
2204            # Brute force assignment of restricted rate
2205            for k in self.exchange_indices:
2206                self.update[k] += rate
2207
2208
2209    def update_rate(self, t):
2210        """Virtual method allowing local modifications by writing an
2211        overriding version in descendant
2212       
2213        """
2214        if callable(self.rate):
2215            rate = self.rate(t)
2216        else:
2217            rate = self.rate
2218
2219        return rate
2220
2221
2222    def get_quantity_values(self, quantity_name=None):
2223        """Return values for specified quantity restricted to opening
2224       
2225        Optionally a quantity name can be specified if values from another
2226        quantity is sought
2227        """
2228       
2229        if quantity_name is None:
2230            quantity_name = self.quantity_name
2231           
2232        q = self.domain.quantities[quantity_name]
2233        return q.get_values(location='centroids', 
2234                            indices=self.exchange_indices)
2235   
2236
2237    def set_quantity_values(self, val, quantity_name=None):
2238        """Set values for specified quantity restricted to opening
2239       
2240        Optionally a quantity name can be specified if values from another
2241        quantity is sought       
2242        """
2243
2244        if quantity_name is None:
2245            quantity_name = self.quantity_name
2246                   
2247        q = self.domain.quantities[self.quantity_name]               
2248        q.set_values(val, 
2249                     location='centroids', 
2250                     indices=self.exchange_indices)   
2251
2252
2253
2254class Rainfall(General_forcing):
2255    """Class Rainfall - general 'rain over entire domain' forcing term.
2256   
2257    Used for implementing Rainfall over the entire domain.
2258       
2259        Current Limited to only One Gauge..
2260       
2261        Need to add Spatial Varying Capability
2262        (This module came from copying and amending the Inflow Code)
2263   
2264    Rainfall(rain)
2265
2266    domain   
2267    rain [mm/s]:  Total rain rate over the specified domain. 
2268                  NOTE: Raingauge Data needs to reflect the time step.
2269                  IE: if Gauge is mm read at a time step, then the input
2270                  here is as mm/(timeStep) so 10mm in 5minutes becomes
2271                  10/(5x60) = 0.0333mm/s.
2272       
2273       
2274                  This parameter can be either a constant or a
2275                  function of time. Positive values indicate inflow,
2276                  negative values indicate outflow.
2277                  (and be used for Infiltration - Write Seperate Module)
2278                  The specified flow will be divided by the area of
2279                  the inflow region and then applied to update the
2280                  stage quantity.
2281                 
2282                  Note also that any reference to the internal attribute 'rate'
2283                  later will use the one converted to m/s.
2284
2285    polygon: Specifies a polygon to restrict the rainfall.
2286   
2287    Examples
2288    How to put them in a run File...
2289       
2290    #------------------------------------------------------------------------
2291    # Setup specialised forcing terms
2292    #------------------------------------------------------------------------
2293    # This is the new element implemented by Ole and Rudy to allow direct
2294    # input of Rainfall in mm/s
2295
2296    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms')) 
2297                        # Note need path to File in String.
2298                        # Else assumed in same directory
2299
2300    domain.forcing_terms.append(catchmentrainfall)
2301    """
2302
2303   
2304    def __init__(self,
2305                 domain,
2306                 rate=0.0,
2307                 center=None, radius=None,
2308                 polygon=None,
2309                 default_rate=None,                 
2310                 verbose=False):
2311
2312        # Converting mm/s to m/s to apply in ANUGA)
2313        if callable(rate):
2314            rain = lambda t: rate(t)/1000.0
2315        else:
2316            rain = rate/1000.0
2317
2318        if default_rate is not None:   
2319            if callable(default_rate):
2320                default_rain = lambda t: default_rate(t)/1000.0
2321            else:
2322                default_rain = default_rate/1000.0
2323        else:
2324            default_rain = None
2325
2326
2327           
2328           
2329        General_forcing.__init__(self,
2330                                 domain,
2331                                 'stage',
2332                                 rate=rain,
2333                                 center=center, radius=radius,
2334                                 polygon=polygon,
2335                                 default_rate=default_rain,
2336                                 verbose=verbose)
2337
2338       
2339
2340
2341
2342
2343class Inflow(General_forcing):
2344    """Class Inflow - general 'rain and drain' forcing term.
2345   
2346    Useful for implementing flows in and out of the domain.
2347   
2348    Inflow(flow, center, radius, polygon)
2349
2350    domain
2351    rate [m^3/s]: Total flow rate over the specified area. 
2352                  This parameter can be either a constant or a
2353                  function of time. Positive values indicate inflow,
2354                  negative values indicate outflow.
2355                  The specified flow will be divided by the area of
2356                  the inflow region and then applied to update stage.     
2357    center [m]: Coordinates at center of flow point
2358    radius [m]: Size of circular area
2359    polygon:    Arbitrary polygon.
2360
2361    Either center, radius or polygon must be specified
2362   
2363    Examples
2364
2365    # Constant drain at 0.003 m^3/s.
2366    # The outflow area is 0.07**2*pi=0.0154 m^2
2367    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
2368    #                                     
2369    Inflow((0.7, 0.4), 0.07, -0.003)
2370
2371
2372    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
2373    # The inflow area is 0.03**2*pi = 0.00283 m^2
2374    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s     
2375    # over the specified area
2376    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
2377
2378
2379    #------------------------------------------------------------------------
2380    # Setup specialised forcing terms
2381    #------------------------------------------------------------------------
2382    # This is the new element implemented by Ole to allow direct input
2383    # of Inflow in m^3/s
2384
2385    hydrograph = Inflow(center=(320, 300), radius=10,
2386                        rate=file_function('Q/QPMF_Rot_Sub13.tms'))
2387
2388    domain.forcing_terms.append(hydrograph)
2389   
2390    """
2391
2392
2393    def __init__(self,
2394                 domain,
2395                 rate=0.0,
2396                 center=None, radius=None,
2397                 polygon=None,
2398                 default_rate=None,
2399                 verbose=False):                 
2400
2401
2402        # Create object first to make area is available
2403        General_forcing.__init__(self,
2404                                 domain,
2405                                 'stage',
2406                                 rate=rate,
2407                                 center=center, radius=radius,
2408                                 polygon=polygon,
2409                                 default_rate=default_rate,
2410                                 verbose=verbose)
2411
2412    def update_rate(self, t):
2413        """Virtual method allowing local modifications by writing an
2414        overriding version in descendant
2415
2416        This one converts m^3/s to m/s which can be added directly
2417        to 'stage' in ANUGA
2418        """
2419
2420        if callable(self.rate):
2421            _rate = self.rate(t)/self.exchange_area
2422        else:
2423            _rate = self.rate/self.exchange_area
2424
2425        return _rate
2426
2427
2428
2429
2430#------------------
2431# Initialise module
2432#------------------
2433
2434
2435from anuga.utilities import compile
2436if compile.can_use_C_extension('shallow_water_ext.c'):
2437    # Underlying C implementations can be accessed
2438
2439    from shallow_water_ext import rotate, assign_windfield_values
2440else:
2441    msg = 'C implementations could not be accessed by %s.\n ' %__file__
2442    msg += 'Make sure compile_all.py has been run as described in '
2443    msg += 'the ANUGA installation guide.'
2444    raise Exception, msg
2445
2446
2447# Optimisation with psyco
2448from anuga.config import use_psyco
2449if use_psyco:
2450    try:
2451        import psyco
2452    except:
2453        import os
2454        if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
2455            pass
2456            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2457        else:
2458            msg = 'WARNING: psyco (speedup) could not import'+\
2459                  ', you may want to consider installing it'
2460            print msg
2461    else:
2462        psyco.bind(Domain.distribute_to_vertices_and_edges)
2463        psyco.bind(Domain.compute_fluxes)
2464
2465if __name__ == "__main__":
2466    pass
2467
2468
Note: See TracBrowser for help on using the repository browser.