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

Last change on this file since 4735 was 4735, checked in by ole, 17 years ago

Cleaned up some obsolete code

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