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

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

Refactored get_flow_through_cross_section and added a runtime version to
shallow_water_domain. It still needs some work and testing.

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