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

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

Arranged for timestepping statistic for chosen triangle, e.g. one of the
gauges in the Okushiri example.
The underlying function is currently brute force, but OK for now.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 53.2 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), etc
10
11CreationDate: 2004
12
13Description:
14    This module contains a specialisation of class Domain from
15    module domain.py consisting of methods specific to the
16    Shallow Water Wave Equation
17
18    U_t + E_x + G_y = S
19
20    where
21
22    U = [w, uh, vh]
23    E = [uh, u^2h + gh^2/2, uvh]
24    G = [vh, uvh, v^2h + gh^2/2]
25    S represents source terms forcing the system
26    (e.g. gravity, friction, wind stress, ...)
27
28    and _t, _x, _y denote the derivative with respect to t, x and y
29    respectively.
30
31
32    The quantities are
33
34    symbol    variable name    explanation
35    x         x                horizontal distance from origin [m]
36    y         y                vertical distance from origin [m]
37    z         elevation        elevation of bed on which flow is modelled [m]
38    h         height           water height above z [m]
39    w         stage            absolute water level, w = z+h [m]
40    u                          speed in the x direction [m/s]
41    v                          speed in the y direction [m/s]
42    uh        xmomentum        momentum in the x direction [m^2/s]
43    vh        ymomentum        momentum in the y direction [m^2/s]
44
45    eta                        mannings friction coefficient [to appear]
46    nu                         wind stress coefficient [to appear]
47
48    The conserved quantities are w, uh, vh
49
50Reference:
51    Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
52    Christopher Zoppou and Stephen Roberts,
53    Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
54
55    Hydrodynamic modelling of coastal inundation.
56    Nielsen, O., S. Roberts, D. Gray, A. McPherson and A. Hitchman
57    In Zerger, A. and Argent, R.M. (eds) MODSIM 2005 International Congress on
58    Modelling and Simulation. Modelling and Simulation Society of Australia and
59    New Zealand, December 2005, pp. 518-523. ISBN: 0-9758400-2-9.
60    http://www.mssanz.org.au/modsim05/papers/nielsen.pdf
61
62
63SeeAlso:
64    TRAC administration of ANUGA (User Manuals etc) at
65    https://datamining.anu.edu.au/anuga and Subversion repository at
66    $HeadURL: anuga_core/source/anuga/shallow_water/shallow_water_domain.py $
67
68Constraints: See GPL license in the user guide
69Version: 1.0 ($Revision: 4836 $)
70ModifiedBy:
71    $Author: ole $
72    $Date: 2007-11-20 06:38:57 +0000 (Tue, 20 Nov 2007) $
73
74"""
75
76# Subversion keywords:
77#
78# $LastChangedDate: 2007-11-20 06:38:57 +0000 (Tue, 20 Nov 2007) $
79# $LastChangedRevision: 4836 $
80# $LastChangedBy: ole $
81
82from Numeric import zeros, ones, Float, array, sum, size
83from Numeric import compress, arange
84
85
86from anuga.abstract_2d_finite_volumes.domain import Domain as Generic_Domain
87from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
88     import Boundary
89from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
90     import File_boundary
91from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
92     import Dirichlet_boundary
93from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
94     import Time_boundary
95from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
96     import Transmissive_boundary
97
98from anuga.utilities.numerical_tools import gradient, mean
99from anuga.config import minimum_storable_height
100from anuga.config import minimum_allowed_height, maximum_allowed_speed
101from anuga.config import g, epsilon, beta_h, beta_w, beta_w_dry,\
102     beta_uh, beta_uh_dry, beta_vh, beta_vh_dry, tight_slope_limiters
103from anuga.config import alpha_balance
104from anuga.config import optimise_dry_cells
105
106#---------------------
107# Shallow water domain
108#---------------------
109class Domain(Generic_Domain):
110
111    conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
112    other_quantities = ['elevation', 'friction']
113   
114    def __init__(self,
115                 coordinates=None,
116                 vertices=None,
117                 boundary=None,
118                 tagged_elements=None,
119                 geo_reference=None,
120                 use_inscribed_circle=False,
121                 mesh_filename=None,
122                 use_cache=False,
123                 verbose=False,
124                 full_send_dict=None,
125                 ghost_recv_dict=None,
126                 processor=0,
127                 numproc=1,
128                 number_of_full_nodes=None,
129                 number_of_full_triangles=None):
130
131
132        other_quantities = ['elevation', 'friction']
133        Generic_Domain.__init__(self,
134                                coordinates,
135                                vertices,
136                                boundary,
137                                Domain.conserved_quantities,
138                                Domain.other_quantities,
139                                tagged_elements,
140                                geo_reference,
141                                use_inscribed_circle,
142                                mesh_filename,
143                                use_cache,
144                                verbose,
145                                full_send_dict,
146                                ghost_recv_dict,
147                                processor,
148                                numproc,
149                                number_of_full_nodes=number_of_full_nodes,
150                                number_of_full_triangles=number_of_full_triangles) 
151
152
153        self.set_minimum_allowed_height(minimum_allowed_height)
154       
155        self.maximum_allowed_speed = maximum_allowed_speed
156        self.g = g
157        self.beta_w      = beta_w
158        self.beta_w_dry  = beta_w_dry
159        self.beta_uh     = beta_uh
160        self.beta_uh_dry = beta_uh_dry
161        self.beta_vh     = beta_vh
162        self.beta_vh_dry = beta_vh_dry
163        self.beta_h      = beta_h
164        self.alpha_balance = alpha_balance
165
166        self.tight_slope_limiters = tight_slope_limiters
167        self.optimise_dry_cells = optimise_dry_cells
168
169        self.forcing_terms.append(manning_friction_implicit)
170        self.forcing_terms.append(gravity)
171
172        # Stored output
173        self.store = True
174        self.format = 'sww'
175        self.set_store_vertices_uniquely(False)
176        self.minimum_storable_height = minimum_storable_height
177        self.quantities_to_be_stored = ['stage','xmomentum','ymomentum']
178       
179               
180
181    def set_all_limiters(self, beta):
182        """Shorthand to assign one constant value [0,1[ to all limiters.
183        0 Corresponds to first order, where as larger values make use of
184        the second order scheme.
185        """
186
187        self.beta_w      = beta
188        self.beta_w_dry  = beta
189        self.beta_uh     = beta
190        self.beta_uh_dry = beta
191        self.beta_vh     = beta
192        self.beta_vh_dry = beta
193        self.beta_h      = beta
194       
195
196    def set_store_vertices_uniquely(self, flag, reduction=None):
197        """Decide whether vertex values should be stored uniquely as
198        computed in the model or whether they should be reduced to one
199        value per vertex using self.reduction.
200        """
201
202        # FIXME (Ole): how about using the word continuous vertex values?
203        self.smooth = not flag
204
205        # Reduction operation for get_vertex_values
206        if reduction is None:
207            self.reduction = mean
208            #self.reduction = min  #Looks better near steep slopes
209
210
211    def set_minimum_storable_height(self, minimum_storable_height):
212        """
213        Set the minimum depth that will be recognised when writing
214        to an sww file. This is useful for removing thin water layers
215        that seems to be caused by friction creep.
216
217        The minimum allowed sww depth is in meters.
218        """
219        self.minimum_storable_height = minimum_storable_height
220
221
222    def set_minimum_allowed_height(self, minimum_allowed_height):
223        """
224        Set the minimum depth that will be recognised in the numerical
225        scheme
226
227        The minimum allowed depth is in meters.
228
229        The parameter H0 (Minimal height for flux computation)
230        is also set by this function
231        """
232
233        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
234
235        #FIXME (Ole): Maybe use histogram to identify isolated extreme speeds
236        #and deal with them adaptively similarly to how we used to use 1 order
237        #steps to recover.
238        self.minimum_allowed_height = minimum_allowed_height
239        self.H0 = minimum_allowed_height   
240       
241
242    def set_maximum_allowed_speed(self, maximum_allowed_speed):
243        """
244        Set the maximum particle speed that is allowed in water
245        shallower than minimum_allowed_height. This is useful for
246        controlling speeds in very thin layers of water and at the same time
247        allow some movement avoiding pooling of water.
248
249        """
250        self.maximum_allowed_speed = maximum_allowed_speed
251
252
253    def set_points_file_block_line_size(self,points_file_block_line_size):
254        """
255        Set the minimum depth that will be recognised when writing
256        to an sww file. This is useful for removing thin water layers
257        that seems to be caused by friction creep.
258
259        The minimum allowed sww depth is in meters.
260        """
261        self.points_file_block_line_size = points_file_block_line_size
262       
263       
264    def set_quantities_to_be_stored(self, q):
265        """Specify which quantities will be stored in the sww file.
266
267        q must be either:
268          - the name of a quantity
269          - a list of quantity names
270          - None
271
272        In the two first cases, the named quantities will be stored at
273        each yieldstep (This is in addition to the quantities elevation
274        and friction)
275       
276        If q is None, storage will be switched off altogether.
277        """
278
279
280        if q is None:
281            self.quantities_to_be_stored = []
282            self.store = False
283            return
284
285        if isinstance(q, basestring):
286            q = [q] # Turn argument into a list
287
288        # Check correcness
289        for quantity_name in q:
290            msg = 'Quantity %s is not a valid conserved quantity'\
291                  %quantity_name
292           
293            assert quantity_name in self.conserved_quantities, msg
294
295        self.quantities_to_be_stored = q
296
297
298
299    def get_wet_elements(self, indices=None):
300        """Return indices for elements where h > minimum_allowed_height
301
302        Optional argument:
303            indices is the set of element ids that the operation applies to.
304
305        Usage:
306            indices = get_wet_elements()
307
308        Note, centroid values are used for this operation           
309        """
310
311        # Water depth below which it is considered to be 0 in the model
312        # FIXME (Ole): Allow this to be specified as a keyword argument as well
313        from anuga.config import minimum_allowed_height
314
315       
316        elevation = self.get_quantity('elevation').\
317                    get_values(location='centroids', indices=indices)
318        stage = self.get_quantity('stage').\
319                get_values(location='centroids', indices=indices)       
320        depth = stage - elevation
321
322        # Select indices for which depth > 0
323        wet_indices = compress(depth > minimum_allowed_height,
324                               arange(len(depth)))
325        return wet_indices
326
327
328    def get_maximum_inundation_elevation(self, indices=None):
329        """Return highest elevation where h > 0
330
331        Optional argument:
332            indices is the set of element ids that the operation applies to.
333
334        Usage:
335            q = get_maximum_inundation_elevation()
336
337        Note, centroid values are used for this operation           
338        """
339
340        wet_elements = self.get_wet_elements(indices)
341        return self.get_quantity('elevation').\
342               get_maximum_value(indices=wet_elements)
343
344
345    def get_maximum_inundation_location(self, indices=None):
346        """Return location of highest elevation where h > 0
347
348        Optional argument:
349            indices is the set of element ids that the operation applies to.
350
351        Usage:
352            q = get_maximum_inundation_location()
353
354        Note, centroid values are used for this operation           
355        """
356
357        wet_elements = self.get_wet_elements(indices)
358        return self.get_quantity('elevation').\
359               get_maximum_location(indices=wet_elements)   
360
361    def check_integrity(self):
362        Generic_Domain.check_integrity(self)
363
364        #Check that we are solving the shallow water wave equation
365
366        msg = 'First conserved quantity must be "stage"'
367        assert self.conserved_quantities[0] == 'stage', msg
368        msg = 'Second conserved quantity must be "xmomentum"'
369        assert self.conserved_quantities[1] == 'xmomentum', msg
370        msg = 'Third conserved quantity must be "ymomentum"'
371        assert self.conserved_quantities[2] == 'ymomentum', msg
372
373    def extrapolate_second_order_sw(self):
374        #Call correct module function
375        #(either from this module or C-extension)
376        extrapolate_second_order_sw(self)
377
378    def compute_fluxes(self):
379        #Call correct module function
380        #(either from this module or C-extension)
381        compute_fluxes(self)
382
383    def distribute_to_vertices_and_edges(self):
384        # Call correct module function
385        # (either from this module or C-extension)
386        distribute_to_vertices_and_edges(self)
387
388
389
390    def evolve(self,
391               yieldstep = None,
392               finaltime = None,
393               duration = None,
394               skip_initial_step = False):
395        """Specialisation of basic evolve method from parent class
396        """
397
398        # Call check integrity here rather than from user scripts
399        # self.check_integrity()
400
401        msg = 'Parameter beta_h must be in the interval [0, 1['
402        assert 0 <= self.beta_h <= 1.0, msg
403        msg = 'Parameter beta_w must be in the interval [0, 1['
404        assert 0 <= self.beta_w <= 1.0, msg
405
406
407        # Initial update of vertex and edge values before any STORAGE
408        # and or visualisation
409        # This is done again in the initialisation of the Generic_Domain
410        # evolve loop but we do it here to ensure the values are ok for storage
411        self.distribute_to_vertices_and_edges()
412
413        if self.store is True and self.time == 0.0:
414            self.initialise_storage()
415            # print 'Storing results in ' + self.writer.filename
416        else:
417            pass
418            # print 'Results will not be stored.'
419            # print 'To store results set domain.store = True'
420            # FIXME: Diagnostic output should be controlled by
421            # a 'verbose' flag living in domain (or in a parent class)
422
423        # Call basic machinery from parent class
424        for t in Generic_Domain.evolve(self,
425                                       yieldstep=yieldstep,
426                                       finaltime=finaltime,
427                                       duration=duration,
428                                       skip_initial_step=skip_initial_step):
429
430            # Store model data, e.g. for subsequent visualisation
431            if self.store is True:
432                self.store_timestep(self.quantities_to_be_stored)
433
434            # FIXME: Could maybe be taken from specified list
435            # of 'store every step' quantities
436
437            # Pass control on to outer loop for more specific actions
438            yield(t)
439
440
441    def initialise_storage(self):
442        """Create and initialise self.writer object for storing data.
443        Also, save x,y and bed elevation
444        """
445
446        from anuga.shallow_water.data_manager import get_dataobject
447
448        # Initialise writer
449        self.writer = get_dataobject(self, mode = 'w')
450
451        # Store vertices and connectivity
452        self.writer.store_connectivity()
453
454
455    def store_timestep(self, name):
456        """Store named quantity and time.
457
458        Precondition:
459           self.write has been initialised
460        """
461        self.writer.store_timestep(name)
462
463       
464    def timestepping_statistics(self,
465                                track_speeds=False,
466                                triangle_id=None):       
467        """Return string with time stepping statistics for printing or logging
468
469        Optional boolean keyword track_speeds decides whether to report
470        location of smallest timestep as well as a histogram and percentile
471        report.
472        """
473
474        from Numeric import sqrt
475        from anuga.config import epsilon, g               
476
477
478        # Call basic machinery from parent class
479        msg = Generic_Domain.timestepping_statistics(self,
480                                                     track_speeds,
481                                                     triangle_id)
482
483        if track_speeds is True:
484
485            # qwidth determines the text field used for quantities
486            qwidth = self.qwidth
487       
488            # Selected triangle
489            k = self.k
490
491            # Report some derived quantities at vertices, edges and centroid
492            # specific to the shallow water wave equation
493
494            z = self.quantities['elevation']
495            w = self.quantities['stage']           
496
497            Vw = w.get_values(location='vertices', indices=[k])[0]
498            Ew = w.get_values(location='edges', indices=[k])[0]
499            Cw = w.get_values(location='centroids', indices=[k])
500
501            Vz = z.get_values(location='vertices', indices=[k])[0]
502            Ez = z.get_values(location='edges', indices=[k])[0]
503            Cz = z.get_values(location='centroids', indices=[k])                             
504               
505
506            name = 'depth'
507            Vh = Vw-Vz
508            Eh = Ew-Ez
509            Ch = Cw-Cz
510           
511            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
512                 %(name.ljust(qwidth), Vh[0], Vh[1], Vh[2])
513           
514            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
515                 %(name.ljust(qwidth), Eh[0], Eh[1], Eh[2])
516           
517            s += '    %s: centroid_value = %.4f\n'\
518                 %(name.ljust(qwidth), Ch[0])                               
519           
520            msg += s
521
522            uh = self.quantities['xmomentum']
523            vh = self.quantities['ymomentum']
524
525            Vuh = uh.get_values(location='vertices', indices=[k])[0]
526            Euh = uh.get_values(location='edges', indices=[k])[0]
527            Cuh = uh.get_values(location='centroids', indices=[k])
528           
529            Vvh = vh.get_values(location='vertices', indices=[k])[0]
530            Evh = vh.get_values(location='edges', indices=[k])[0]
531            Cvh = vh.get_values(location='centroids', indices=[k])
532
533            # Speeds in each direction
534            Vu = Vuh/(Vh + epsilon)
535            Eu = Euh/(Eh + epsilon)
536            Cu = Cuh/(Ch + epsilon)           
537            name = 'U'
538            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
539                 %(name.ljust(qwidth), Vu[0], Vu[1], Vu[2])
540           
541            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
542                 %(name.ljust(qwidth), Eu[0], Eu[1], Eu[2])
543           
544            s += '    %s: centroid_value = %.4f\n'\
545                 %(name.ljust(qwidth), Cu[0])                               
546           
547            msg += s
548
549            Vv = Vvh/(Vh + epsilon)
550            Ev = Evh/(Eh + epsilon)
551            Cv = Cvh/(Ch + epsilon)           
552            name = 'V'
553            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
554                 %(name.ljust(qwidth), Vv[0], Vv[1], Vv[2])
555           
556            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
557                 %(name.ljust(qwidth), Ev[0], Ev[1], Ev[2])
558           
559            s += '    %s: centroid_value = %.4f\n'\
560                 %(name.ljust(qwidth), Cv[0])                               
561           
562            msg += s
563
564
565            # Froude number in each direction
566            name = 'Froude (x)'
567            Vfx = Vu/(sqrt(g*Vh) + epsilon)
568            Efx = Eu/(sqrt(g*Eh) + epsilon)
569            Cfx = Cu/(sqrt(g*Ch) + epsilon)
570           
571            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
572                 %(name.ljust(qwidth), Vfx[0], Vfx[1], Vfx[2])
573           
574            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
575                 %(name.ljust(qwidth), Efx[0], Efx[1], Efx[2])
576           
577            s += '    %s: centroid_value = %.4f\n'\
578                 %(name.ljust(qwidth), Cfx[0])                               
579           
580            msg += s
581
582
583            name = 'Froude (y)'
584            Vfy = Vv/(sqrt(g*Vh) + epsilon)
585            Efy = Ev/(sqrt(g*Eh) + epsilon)
586            Cfy = Cv/(sqrt(g*Ch) + epsilon)
587           
588            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
589                 %(name.ljust(qwidth), Vfy[0], Vfy[1], Vfy[2])
590           
591            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
592                 %(name.ljust(qwidth), Efy[0], Efy[1], Efy[2])
593           
594            s += '    %s: centroid_value = %.4f\n'\
595                 %(name.ljust(qwidth), Cfy[0])                               
596           
597            msg += s           
598
599               
600
601        return msg
602       
603       
604
605#=============== End of class Shallow Water Domain ===============================
606
607
608#-----------------
609# Flux computation
610#-----------------
611
612def compute_fluxes(domain):
613    """Compute all fluxes and the timestep suitable for all volumes
614    in domain.
615
616    Compute total flux for each conserved quantity using "flux_function"
617
618    Fluxes across each edge are scaled by edgelengths and summed up
619    Resulting flux is then scaled by area and stored in
620    explicit_update for each of the three conserved quantities
621    stage, xmomentum and ymomentum
622
623    The maximal allowable speed computed by the flux_function for each volume
624    is converted to a timestep that must not be exceeded. The minimum of
625    those is computed as the next overall timestep.
626
627    Post conditions:
628      domain.explicit_update is reset to computed flux values
629      domain.timestep is set to the largest step satisfying all volumes.
630   
631
632    This wrapper calls the underlying C version of compute fluxes
633    """
634
635    import sys
636
637    N = len(domain) # number_of_triangles
638
639    # Shortcuts
640    Stage = domain.quantities['stage']
641    Xmom = domain.quantities['xmomentum']
642    Ymom = domain.quantities['ymomentum']
643    Bed = domain.quantities['elevation']
644
645    timestep = float(sys.maxint)
646    from shallow_water_ext import\
647         compute_fluxes_ext_central as compute_fluxes_ext
648
649
650    flux_timestep = compute_fluxes_ext(timestep,
651                                       domain.epsilon,
652                                       domain.H0,
653                                       domain.g,
654                                       domain.neighbours,
655                                       domain.neighbour_edges,
656                                       domain.normals,
657                                       domain.edgelengths,
658                                       domain.radii,
659                                       domain.areas,
660                                       domain.tri_full_flag,
661                                       Stage.edge_values,
662                                       Xmom.edge_values,
663                                       Ymom.edge_values,
664                                       Bed.edge_values,
665                                       Stage.boundary_values,
666                                       Xmom.boundary_values,
667                                       Ymom.boundary_values,
668                                       Stage.explicit_update,
669                                       Xmom.explicit_update,
670                                       Ymom.explicit_update,
671                                       domain.already_computed_flux,
672                                       domain.max_speed,
673                                       int(domain.optimise_dry_cells))
674
675    domain.flux_timestep = flux_timestep
676
677
678
679#---------------------------------------
680# Module functions for gradient limiting
681#---------------------------------------
682
683
684# MH090605 The following method belongs to the shallow_water domain class
685# see comments in the corresponding method in shallow_water_ext.c
686def extrapolate_second_order_sw(domain):
687    """Wrapper calling C version of extrapolate_second_order_sw
688    """
689    import sys
690
691    N = len(domain) # number_of_triangles
692
693    # Shortcuts
694    Stage = domain.quantities['stage']
695    Xmom = domain.quantities['xmomentum']
696    Ymom = domain.quantities['ymomentum']
697    Elevation = domain.quantities['elevation']
698
699    from shallow_water_ext import extrapolate_second_order_sw as extrapol2
700    extrapol2(domain,
701              domain.surrogate_neighbours,
702              domain.number_of_boundaries,
703              domain.centroid_coordinates,
704              Stage.centroid_values,
705              Xmom.centroid_values,
706              Ymom.centroid_values,
707              Elevation.centroid_values,
708              domain.vertex_coordinates,
709              Stage.vertex_values,
710              Xmom.vertex_values,
711              Ymom.vertex_values,
712              Elevation.vertex_values,
713              int(domain.optimise_dry_cells))
714
715
716def distribute_to_vertices_and_edges(domain):
717    """Distribution from centroids to vertices specific to the
718    shallow water wave
719    equation.
720
721    It will ensure that h (w-z) is always non-negative even in the
722    presence of steep bed-slopes by taking a weighted average between shallow
723    and deep cases.
724
725    In addition, all conserved quantities get distributed as per either a
726    constant (order==1) or a piecewise linear function (order==2).
727
728    FIXME: more explanation about removal of artificial variability etc
729
730    Precondition:
731      All quantities defined at centroids and bed elevation defined at
732      vertices.
733
734    Postcondition
735      Conserved quantities defined at vertices
736
737    """
738
739    from anuga.config import optimised_gradient_limiter
740
741    # Remove very thin layers of water
742    protect_against_infinitesimal_and_negative_heights(domain)
743
744    # Extrapolate all conserved quantities
745    if optimised_gradient_limiter:
746        # MH090605 if second order,
747        # perform the extrapolation and limiting on
748        # all of the conserved quantities
749
750        if (domain._order_ == 1):
751            for name in domain.conserved_quantities:
752                Q = domain.quantities[name]
753                Q.extrapolate_first_order()
754        elif domain._order_ == 2:
755            domain.extrapolate_second_order_sw()
756        else:
757            raise 'Unknown order'
758    else:
759        # Old code:
760        for name in domain.conserved_quantities:
761            Q = domain.quantities[name]
762
763            if domain._order_ == 1:
764                Q.extrapolate_first_order()
765            elif domain._order_ == 2:
766
767                # Experiment
768                #if name == 'stage':
769                #    #print name, 'second'
770                #    Q.extrapolate_second_order()
771                #    Q.limit()
772                #else:
773                #    #print name, 'first'               
774                #    Q.extrapolate_first_order()
775                #    #Q.extrapolate_second_order()
776                #    #Q.limit()               
777               
778                Q.extrapolate_second_order()
779                Q.limit()
780            else:
781                raise 'Unknown order'
782
783
784    #Take bed elevation into account when water heights are small
785    balance_deep_and_shallow(domain)
786
787    #Compute edge values by interpolation
788    for name in domain.conserved_quantities:
789        Q = domain.quantities[name]
790        Q.interpolate_from_vertices_to_edges()
791
792
793def protect_against_infinitesimal_and_negative_heights(domain):
794    """Protect against infinitesimal heights and associated high velocities
795    """
796
797    # Shortcuts
798    wc = domain.quantities['stage'].centroid_values
799    zc = domain.quantities['elevation'].centroid_values
800    xmomc = domain.quantities['xmomentum'].centroid_values
801    ymomc = domain.quantities['ymomentum'].centroid_values
802
803    from shallow_water_ext import protect
804
805    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
806            domain.epsilon, wc, zc, xmomc, ymomc)
807
808
809def h_limiter(domain):
810    """Limit slopes for each volume to eliminate artificial variance
811    introduced by e.g. second order extrapolator
812
813    limit on h = w-z
814
815    This limiter depends on two quantities (w,z) so it resides within
816    this module rather than within quantity.py
817
818    Wrapper for c-extension
819    """
820
821    N = len(domain) # number_of_triangles
822    beta_h = domain.beta_h
823
824    # Shortcuts
825    wc = domain.quantities['stage'].centroid_values
826    zc = domain.quantities['elevation'].centroid_values
827    hc = wc - zc
828
829    wv = domain.quantities['stage'].vertex_values
830    zv = domain.quantities['elevation'].vertex_values
831    hv = wv - zv
832
833    #Call C-extension
834    from shallow_water_ext import h_limiter_sw
835    hvbar = h_limiter_sw(domain, hc, hv)
836
837    return hvbar
838
839
840def balance_deep_and_shallow(domain):
841    """Compute linear combination between stage as computed by
842    gradient-limiters limiting using w, and stage computed by
843    gradient-limiters limiting using h (h-limiter).
844    The former takes precedence when heights are large compared to the
845    bed slope while the latter takes precedence when heights are
846    relatively small.  Anything in between is computed as a balanced
847    linear combination in order to avoid numerical disturbances which
848    would otherwise appear as a result of hard switching between
849    modes.
850
851    Wrapper for C implementation
852    """
853
854    # FIXME (Ole): I reckon this can be simplified significantly:
855    #
856    # Always use beta_h == 0, and phase it out.
857    # Compute hc and hv in the c-code
858    # Omit updating xmomv
859    #
860    from shallow_water_ext import balance_deep_and_shallow as balance_deep_and_shallow_c
861   
862    # Shortcuts
863    wc = domain.quantities['stage'].centroid_values
864    zc = domain.quantities['elevation'].centroid_values
865
866    wv = domain.quantities['stage'].vertex_values
867    zv = domain.quantities['elevation'].vertex_values
868
869    # Momentums at centroids
870    xmomc = domain.quantities['xmomentum'].centroid_values
871    ymomc = domain.quantities['ymomentum'].centroid_values
872
873    # Momentums at vertices
874    xmomv = domain.quantities['xmomentum'].vertex_values
875    ymomv = domain.quantities['ymomentum'].vertex_values
876
877    # Limit h
878    if domain.beta_h > 0:
879        hvbar = h_limiter(domain)
880       
881        balance_deep_and_shallow_c(domain, domain.beta_h,
882                                   wc, zc, wv, zv, hvbar,
883                                   xmomc, ymomc, xmomv, ymomv)       
884    else:
885        # print 'Using first order h-limiter'
886        # FIXME: Pass wc in for now - it will be ignored.
887       
888        # This is how one would make a first order h_limited value
889        # as in the old balancer (pre 17 Feb 2005):
890        #  If we wish to hard wire this, one should modify the C-code
891        # from Numeric import zeros, Float
892        # hvbar = zeros( (len(wc), 3), Float)
893        # for i in range(3):
894        #     hvbar[:,i] = wc[:] - zc[:]
895
896        balance_deep_and_shallow_c(domain, domain.beta_h,
897                                   wc, zc, wv, zv, wc, 
898                                   xmomc, ymomc, xmomv, ymomv)
899
900
901
902
903#------------------------------------------------------------------
904# Boundary conditions - specific to the shallow water wave equation
905#------------------------------------------------------------------
906class Reflective_boundary(Boundary):
907    """Reflective boundary returns same conserved quantities as
908    those present in its neighbour volume but reflected.
909
910    This class is specific to the shallow water equation as it
911    works with the momentum quantities assumed to be the second
912    and third conserved quantities.
913    """
914
915    def __init__(self, domain = None):
916        Boundary.__init__(self)
917
918        if domain is None:
919            msg = 'Domain must be specified for reflective boundary'
920            raise msg
921
922        # Handy shorthands
923        self.stage   = domain.quantities['stage'].edge_values
924        self.xmom    = domain.quantities['xmomentum'].edge_values
925        self.ymom    = domain.quantities['ymomentum'].edge_values
926        self.normals = domain.normals
927
928        self.conserved_quantities = zeros(3, Float)
929
930    def __repr__(self):
931        return 'Reflective_boundary'
932
933
934    def evaluate(self, vol_id, edge_id):
935        """Reflective boundaries reverses the outward momentum
936        of the volume they serve.
937        """
938
939        q = self.conserved_quantities
940        q[0] = self.stage[vol_id, edge_id]
941        q[1] = self.xmom[vol_id, edge_id]
942        q[2] = self.ymom[vol_id, edge_id]
943
944        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
945
946
947        r = rotate(q, normal, direction = 1)
948        r[1] = -r[1]
949        q = rotate(r, normal, direction = -1)
950
951        return q
952
953
954
955class Transmissive_Momentum_Set_Stage_boundary(Boundary):
956    """Returns same momentum conserved quantities as
957    those present in its neighbour volume.
958    Sets stage by specifying a function f of time which may either be a
959    vector function or a scalar function
960
961    Example:
962
963    def waveform(t):
964        return sea_level + normalized_amplitude/cosh(t-25)**2
965
966    Bts = Transmissive_Momentum_Set_Stage_boundary(domain, waveform)
967   
968
969    Underlying domain must be specified when boundary is instantiated
970    """
971
972    def __init__(self, domain = None, function=None):
973        Boundary.__init__(self)
974
975        if domain is None:
976            msg = 'Domain must be specified for this type boundary'
977            raise msg
978
979        if function is None:
980            msg = 'Function must be specified for this type boundary'
981            raise msg
982
983        self.domain   = domain
984        self.function = function
985
986    def __repr__(self):
987        return 'Transmissive_Momentum_Set_Stage_boundary(%s)' %self.domain
988
989    def evaluate(self, vol_id, edge_id):
990        """Transmissive Momentum Set Stage boundaries return the edge momentum
991        values of the volume they serve.
992        """
993
994        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
995        value = self.function(self.domain.time)
996
997        try:
998            x = float(value)
999        except:   
1000            x = float(value[0])
1001           
1002        q[0] = x
1003        return q
1004
1005
1006        # FIXME: Consider this (taken from File_boundary) to allow
1007        # spatial variation
1008        # if vol_id is not None and edge_id is not None:
1009        #     i = self.boundary_indices[ vol_id, edge_id ]
1010        #     return self.F(t, point_id = i)
1011        # else:
1012        #     return self.F(t)
1013
1014
1015
1016class Dirichlet_Discharge_boundary(Boundary):
1017    """
1018    Sets stage (stage0)
1019    Sets momentum (wh0) in the inward normal direction.
1020
1021    Underlying domain must be specified when boundary is instantiated
1022    """
1023
1024    def __init__(self, domain = None, stage0=None, wh0=None):
1025        Boundary.__init__(self)
1026
1027        if domain is None:
1028            msg = 'Domain must be specified for this type boundary'
1029            raise msg
1030
1031        if stage0 is None:
1032            raise 'set stage'
1033
1034        if wh0 is None:
1035            wh0 = 0.0
1036
1037        self.domain   = domain
1038        self.stage0  = stage0
1039        self.wh0 = wh0
1040
1041    def __repr__(self):
1042        return 'Dirichlet_Discharge_boundary(%s)' %self.domain
1043
1044    def evaluate(self, vol_id, edge_id):
1045        """Set discharge in the (inward) normal direction
1046        """
1047
1048        normal = self.domain.get_normal(vol_id,edge_id)
1049        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1050        return q
1051
1052
1053        # FIXME: Consider this (taken from File_boundary) to allow
1054        # spatial variation
1055        # if vol_id is not None and edge_id is not None:
1056        #     i = self.boundary_indices[ vol_id, edge_id ]
1057        #     return self.F(t, point_id = i)
1058        # else:
1059        #     return self.F(t)
1060
1061
1062class Field_boundary(Boundary):
1063    """Set boundary from given field represented in an sww file containing values
1064    for stage, xmomentum and ymomentum.
1065    Optionally, the user can specify mean_stage to offset the stage provided in the
1066    sww file.
1067
1068    This function is a thin wrapper around the generic File_boundary. The
1069    difference between the file_boundary and field_boundary is only that the
1070    field_boundary will allow you to change the level of the stage height when
1071    you read in the boundary condition. This is very useful when running
1072    different tide heights in the same area as you need only to convert one
1073    boundary condition to a SWW file, ideally for tide height of 0 m
1074    (saving disk space). Then you can use field_boundary to read this SWW file
1075    and change the stage height (tide) on the fly depending on the scenario.
1076   
1077    """
1078
1079
1080    def __init__(self, filename, domain,
1081                 mean_stage=0.0,
1082                 time_thinning=1, 
1083                 use_cache=False,
1084                 verbose=False):
1085        """Constructor
1086
1087        filename: Name of sww file
1088        domain: pointer to shallow water domain for which the boundary applies
1089        mean_stage: The mean water level which will be added to stage derived
1090                    from the sww file
1091        time_thinning: Will set how many time steps from the sww file read in
1092                       will be interpolated to the boundary. For example if
1093                       the sww file has 1 second time steps and is 24 hours
1094                       in length it has 86400 time steps. If you set
1095                       time_thinning to 1 it will read all these steps.
1096                       If you set it to 100 it will read every 100th step eg
1097                       only 864 step. This parameter is very useful to increase
1098                       the speed of a model run that you are setting up
1099                       and testing.
1100        use_cache:
1101        verbose:
1102       
1103        """
1104
1105        # Create generic file_boundary object
1106        self.file_boundary = File_boundary(filename, domain,
1107                                           time_thinning=time_thinning,
1108                                           use_cache=use_cache,
1109                                           verbose=verbose)
1110       
1111        # Record information from File_boundary
1112        self.F = self.file_boundary.F
1113        self.domain = self.file_boundary.domain
1114       
1115        # Record mean stage
1116        self.mean_stage = mean_stage
1117
1118
1119    def __repr__(self):
1120        return 'Field boundary'
1121
1122
1123    def evaluate(self, vol_id=None, edge_id=None):
1124        """Return linearly interpolated values based on domain.time
1125
1126        vol_id and edge_id are ignored
1127        """
1128
1129        # Evaluate file boundary
1130        q = self.file_boundary.evaluate(vol_id, edge_id)
1131
1132        # Adjust stage
1133        for j, name in enumerate(self.domain.conserved_quantities):
1134            if name == 'stage':
1135                q[j] += self.mean_stage
1136        return q
1137
1138   
1139
1140#-----------------------
1141# Standard forcing terms
1142#-----------------------
1143
1144def gravity(domain):
1145    """Apply gravitational pull in the presence of bed slope
1146    Wrapper calls underlying C implementation
1147    """
1148
1149    xmom = domain.quantities['xmomentum'].explicit_update
1150    ymom = domain.quantities['ymomentum'].explicit_update
1151
1152    stage = domain.quantities['stage']
1153    elevation = domain.quantities['elevation']
1154
1155    h = stage.centroid_values - elevation.centroid_values
1156    z = elevation.vertex_values
1157
1158    x = domain.get_vertex_coordinates()
1159    g = domain.g
1160   
1161
1162    from shallow_water_ext import gravity as gravity_c
1163    gravity_c(g, h, z, x, xmom, ymom) #, 1.0e-6)
1164
1165
1166
1167def manning_friction_implicit(domain):
1168    """Apply (Manning) friction to water momentum   
1169    Wrapper for c version
1170    """
1171
1172
1173    #print 'Implicit friction'
1174
1175    xmom = domain.quantities['xmomentum']
1176    ymom = domain.quantities['ymomentum']
1177
1178    w = domain.quantities['stage'].centroid_values
1179    z = domain.quantities['elevation'].centroid_values
1180
1181    uh = xmom.centroid_values
1182    vh = ymom.centroid_values
1183    eta = domain.quantities['friction'].centroid_values
1184
1185    xmom_update = xmom.semi_implicit_update
1186    ymom_update = ymom.semi_implicit_update
1187
1188    N = len(domain)
1189    eps = domain.minimum_allowed_height
1190    g = domain.g
1191
1192    from shallow_water_ext import manning_friction as manning_friction_c
1193    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1194
1195
1196def manning_friction_explicit(domain):
1197    """Apply (Manning) friction to water momentum   
1198    Wrapper for c version
1199    """
1200
1201    # print 'Explicit friction'
1202
1203    xmom = domain.quantities['xmomentum']
1204    ymom = domain.quantities['ymomentum']
1205
1206    w = domain.quantities['stage'].centroid_values
1207    z = domain.quantities['elevation'].centroid_values
1208
1209    uh = xmom.centroid_values
1210    vh = ymom.centroid_values
1211    eta = domain.quantities['friction'].centroid_values
1212
1213    xmom_update = xmom.explicit_update
1214    ymom_update = ymom.explicit_update
1215
1216    N = len(domain)
1217    eps = domain.minimum_allowed_height
1218    g = domain.g
1219
1220    from shallow_water_ext import manning_friction as manning_friction_c
1221    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1222
1223
1224# FIXME (Ole): This was implemented for use with one of the analytical solutions (Sampson?)
1225# Is it still needed (30 Oct 2007)?
1226def linear_friction(domain):
1227    """Apply linear friction to water momentum
1228
1229    Assumes quantity: 'linear_friction' to be present
1230    """
1231
1232    from math import sqrt
1233
1234    w = domain.quantities['stage'].centroid_values
1235    z = domain.quantities['elevation'].centroid_values
1236    h = w-z
1237
1238    uh = domain.quantities['xmomentum'].centroid_values
1239    vh = domain.quantities['ymomentum'].centroid_values
1240    tau = domain.quantities['linear_friction'].centroid_values
1241
1242    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1243    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1244
1245    N = len(domain) # number_of_triangles
1246    eps = domain.minimum_allowed_height
1247    g = domain.g #Not necessary? Why was this added?
1248
1249    for k in range(N):
1250        if tau[k] >= eps:
1251            if h[k] >= eps:
1252                S = -tau[k]/h[k]
1253
1254                #Update momentum
1255                xmom_update[k] += S*uh[k]
1256                ymom_update[k] += S*vh[k]
1257
1258
1259
1260#---------------------------------
1261# Experimental auxiliary functions
1262#---------------------------------
1263def check_forcefield(f):
1264    """Check that f is either
1265    1: a callable object f(t,x,y), where x and y are vectors
1266       and that it returns an array or a list of same length
1267       as x and y
1268    2: a scalar
1269    """
1270
1271    if callable(f):
1272        N = 3
1273        x = ones(3, Float)
1274        y = ones(3, Float)
1275        try:
1276            q = f(1.0, x=x, y=y)
1277        except Exception, e:
1278            msg = 'Function %s could not be executed:\n%s' %(f, e)
1279            # FIXME: Reconsider this semantics
1280            raise msg
1281
1282        try:
1283            q = array(q).astype(Float)
1284        except:
1285            msg = 'Return value from vector function %s could ' %f
1286            msg += 'not be converted into a Numeric array of floats.\n'
1287            msg += 'Specified function should return either list or array.'
1288            raise msg
1289
1290        # Is this really what we want?
1291        msg = 'Return vector from function %s ' %f
1292        msg += 'must have same lenght as input vectors'
1293        assert len(q) == N, msg
1294
1295    else:
1296        try:
1297            f = float(f)
1298        except:
1299            msg = 'Force field %s must be either a scalar' %f
1300            msg += ' or a vector function'
1301            raise Exception(msg)
1302    return f
1303
1304
1305class Wind_stress:
1306    """Apply wind stress to water momentum in terms of
1307    wind speed [m/s] and wind direction [degrees]
1308    """
1309
1310    def __init__(self, *args, **kwargs):
1311        """Initialise windfield from wind speed s [m/s]
1312        and wind direction phi [degrees]
1313
1314        Inputs v and phi can be either scalars or Python functions, e.g.
1315
1316        W = Wind_stress(10, 178)
1317
1318        #FIXME - 'normal' degrees are assumed for now, i.e. the
1319        vector (1,0) has zero degrees.
1320        We may need to convert from 'compass' degrees later on and also
1321        map from True north to grid north.
1322
1323        Arguments can also be Python functions of t,x,y as in
1324
1325        def speed(t,x,y):
1326            ...
1327            return s
1328
1329        def angle(t,x,y):
1330            ...
1331            return phi
1332
1333        where x and y are vectors.
1334
1335        and then pass the functions in
1336
1337        W = Wind_stress(speed, angle)
1338
1339        The instantiated object W can be appended to the list of
1340        forcing_terms as in
1341
1342        Alternatively, one vector valued function for (speed, angle)
1343        can be applied, providing both quantities simultaneously.
1344        As in
1345        W = Wind_stress(F), where returns (speed, angle) for each t.
1346
1347        domain.forcing_terms.append(W)
1348        """
1349
1350        from anuga.config import rho_a, rho_w, eta_w
1351        from Numeric import array, Float
1352
1353        if len(args) == 2:
1354            s = args[0]
1355            phi = args[1]
1356        elif len(args) == 1:
1357            # Assume vector function returning (s, phi)(t,x,y)
1358            vector_function = args[0]
1359            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1360            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1361        else:
1362           # Assume info is in 2 keyword arguments
1363
1364           if len(kwargs) == 2:
1365               s = kwargs['s']
1366               phi = kwargs['phi']
1367           else:
1368               raise 'Assumes two keyword arguments: s=..., phi=....'
1369
1370        self.speed = check_forcefield(s)
1371        self.phi = check_forcefield(phi)
1372
1373        self.const = eta_w*rho_a/rho_w
1374
1375
1376    def __call__(self, domain):
1377        """Evaluate windfield based on values found in domain
1378        """
1379
1380        from math import pi, cos, sin, sqrt
1381        from Numeric import Float, ones, ArrayType
1382
1383        xmom_update = domain.quantities['xmomentum'].explicit_update
1384        ymom_update = domain.quantities['ymomentum'].explicit_update
1385
1386        N = len(domain) # number_of_triangles
1387        t = domain.time
1388
1389        if callable(self.speed):
1390            xc = domain.get_centroid_coordinates()
1391            s_vec = self.speed(t, xc[:,0], xc[:,1])
1392        else:
1393            # Assume s is a scalar
1394
1395            try:
1396                s_vec = self.speed * ones(N, Float)
1397            except:
1398                msg = 'Speed must be either callable or a scalar: %s' %self.s
1399                raise msg
1400
1401
1402        if callable(self.phi):
1403            xc = domain.get_centroid_coordinates()
1404            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1405        else:
1406            # Assume phi is a scalar
1407
1408            try:
1409                phi_vec = self.phi * ones(N, Float)
1410            except:
1411                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1412                raise msg
1413
1414        assign_windfield_values(xmom_update, ymom_update,
1415                                s_vec, phi_vec, self.const)
1416
1417
1418def assign_windfield_values(xmom_update, ymom_update,
1419                            s_vec, phi_vec, const):
1420    """Python version of assigning wind field to update vectors.
1421    A c version also exists (for speed)
1422    """
1423    from math import pi, cos, sin, sqrt
1424
1425    N = len(s_vec)
1426    for k in range(N):
1427        s = s_vec[k]
1428        phi = phi_vec[k]
1429
1430        # Convert to radians
1431        phi = phi*pi/180
1432
1433        # Compute velocity vector (u, v)
1434        u = s*cos(phi)
1435        v = s*sin(phi)
1436
1437        # Compute wind stress
1438        S = const * sqrt(u**2 + v**2)
1439        xmom_update[k] += S*u
1440        ymom_update[k] += S*v
1441
1442
1443
1444class Rainfall:
1445    """Class Rainfall - general 'rain over entire domain' forcing term.
1446   
1447    Used for implementing Rainfall over the entire domain.
1448       
1449        Current Limited to only One Gauge..
1450       
1451        Need to add Spatial Varying Capability
1452        (This module came from copying and amending the Inflow Code)
1453   
1454    Rainfall(rain)
1455       
1456    rain [mm/s]:  Total rain rate over the specified domain. 
1457                  NOTE: Raingauge Data needs to reflect the time step.
1458                  IE: if Gauge is mm read at a time step, then the input
1459                  here is as mm/(timeStep) so 10mm in 5minutes becomes
1460                  10/(5x60) = 0.0333mm/s.
1461       
1462       
1463                  This parameter can be either a constant or a
1464                  function of time. Positive values indicate inflow,
1465                  negative values indicate outflow.
1466                  (and be used for Infiltration - Write Seperate Module)
1467                  The specified flow will be divided by the area of
1468                  the inflow region and then applied to update the
1469                  quantity in question.
1470   
1471    Examples
1472    How to put them in a run File...
1473       
1474    #--------------------------------------------------------------------------
1475    # Setup specialised forcing terms
1476    #--------------------------------------------------------------------------
1477    # This is the new element implemented by Ole and Rudy to allow direct
1478    # input of Inflow in mm/s
1479
1480    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms')) 
1481                        # Note need path to File in String.
1482                        # Else assumed in same directory
1483
1484    domain.forcing_terms.append(catchmentrainfall)
1485    """
1486
1487    # FIXME (OLE): Add a polygon as an alternative.
1488    # FIXME (AnyOne) : Add various methods to allow spatial variations
1489    # FIXME (OLE): Generalise to all quantities
1490
1491    def __init__(self,
1492                 rain=0.0,
1493                 quantity_name='stage'):
1494
1495        self.rain = rain
1496        self.quantity_name = quantity_name
1497   
1498    def __call__(self, domain):
1499
1500        # Update rainfall
1501        if callable(self.rain):
1502            rain = self.rain(domain.get_time())
1503        else:
1504            rain = self.rain
1505
1506        # Now rain is a number
1507        quantity = domain.quantities[self.quantity_name].explicit_update
1508        quantity[:] += rain/1000  # Converting mm/s to m/s to apply in ANUGA
1509                # 1mm of rainfall is equivalent to 1 litre /m2
1510                # Flow is expressed as m3/s converted to a stage height in (m)
1511               
1512                # Note 1m3 = 1x10^9mm3 (mls)
1513                # or is that m3 to Litres ??? Check this how is it applied !!!
1514
1515
1516class Inflow:
1517    """Class Inflow - general 'rain and drain' forcing term.
1518   
1519    Useful for implementing flows in and out of the domain.
1520   
1521    Inflow(center, radius, flow)
1522   
1523    center [m]: Coordinates at center of flow point
1524    radius [m]: Size of circular area
1525    flow [m^3/s]: Total flow rate over the specified area. 
1526                  This parameter can be either a constant or a
1527                  function of time. Positive values indicate inflow,
1528                  negative values indicate outflow.
1529                  The specified flow will be divided by the area of
1530                  the inflow region and then applied to update the
1531                  quantity in question.
1532   
1533    Examples
1534
1535    # Constant drain at 0.003 m^3/s.
1536    # The outflow area is 0.07**2*pi=0.0154 m^2
1537    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
1538    #                                     
1539    Inflow((0.7, 0.4), 0.07, -0.003)
1540
1541
1542    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
1543    # The inflow area is 0.03**2*pi = 0.00283 m^2
1544    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s     
1545    # over the specified area
1546    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
1547
1548    #--------------------------------------------------------------------------
1549    # Setup specialised forcing terms
1550    #--------------------------------------------------------------------------
1551    # This is the new element implemented by Ole to allow direct input
1552    # of Inflow in m^3/s
1553
1554    hydrograph = Inflow(center=(320, 300), radius=10,
1555                        flow=file_function('Q/QPMF_Rot_Sub13.tms'))
1556
1557    domain.forcing_terms.append(hydrograph)
1558   
1559    """
1560
1561    # FIXME (OLE): Add a polygon as an alternative.
1562    # FIXME (OLE): Generalise to all quantities
1563
1564    def __init__(self,
1565                 center=None, radius=None,
1566                 flow=0.0,
1567                 quantity_name = 'stage'):
1568
1569        from math import pi
1570
1571       
1572                 
1573        if center is not None and radius is not None:
1574            assert len(center) == 2
1575        else:
1576            msg = 'Both center and radius must be specified'
1577            raise Exception, msg
1578   
1579        self.center = center
1580        self.radius = radius
1581        self.area = radius**2*pi
1582        self.flow = flow
1583        self.quantity_name = quantity_name
1584   
1585    def __call__(self, domain):
1586
1587        # Determine indices in flow area
1588        if not hasattr(self, 'indices'):
1589            center = self.center
1590            radius = self.radius
1591           
1592            N = len(domain)   
1593            self.indices = []
1594            coordinates = domain.get_centroid_coordinates()     
1595            for k in range(N):
1596                x, y = coordinates[k,:] # Centroid
1597                if ((x-center[0])**2+(y-center[1])**2) < radius**2:
1598                    self.indices.append(k)   
1599
1600        # Update inflow
1601        if callable(self.flow):
1602            flow = self.flow(domain.get_time())
1603        else:
1604            flow = self.flow
1605
1606        # Now flow is a number
1607       
1608        quantity = domain.quantities[self.quantity_name].explicit_update
1609        for k in self.indices:
1610            quantity[k] += flow/self.area                       
1611
1612
1613#------------------
1614# Initialise module
1615#------------------
1616
1617
1618from anuga.utilities import compile
1619if compile.can_use_C_extension('shallow_water_ext.c'):
1620    # Underlying C implementations can be accessed
1621
1622    from shallow_water_ext import rotate, assign_windfield_values
1623else:
1624    msg = 'C implementations could not be accessed by %s.\n ' %__file__
1625    msg += 'Make sure compile_all.py has been run as described in '
1626    msg += 'the ANUGA installation guide.'
1627    raise Exception, msg
1628
1629
1630# Optimisation with psyco
1631from anuga.config import use_psyco
1632if use_psyco:
1633    try:
1634        import psyco
1635    except:
1636        import os
1637        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1638            pass
1639            #Psyco isn't supported on 64 bit systems, but it doesn't matter
1640        else:
1641            msg = 'WARNING: psyco (speedup) could not import'+\
1642                  ', you may want to consider installing it'
1643            print msg
1644    else:
1645        psyco.bind(Domain.distribute_to_vertices_and_edges)
1646        psyco.bind(Domain.compute_fluxes)
1647
1648if __name__ == "__main__":
1649    pass
1650
1651
Note: See TracBrowser for help on using the repository browser.