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

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

Run time - flow through cross section and some refactoring + clean up

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