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

Last change on this file since 4754 was 4754, checked in by nick, 16 years ago

Add information about "field_boundary", clarifying time_thinning parameter and the difference between "file_boundary"

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 64.4 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: 4754 $)
70ModifiedBy:
71    $Author: nick $
72    $Date: 2007-10-26 01:53:06 +0000 (Fri, 26 Oct 2007) $
73
74"""
75
76#Subversion keywords:
77#
78#$LastChangedDate: 2007-10-26 01:53:06 +0000 (Fri, 26 Oct 2007) $
79#$LastChangedRevision: 4754 $
80#$LastChangedBy: nick $
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. The
1442    difference between the file_boundary and field_boundary is only that the
1443    field_boundary will allow you to change the level of the stage height when
1444    you read in the boundary condition. This is very useful when running
1445    different tide heights in the same area as you need only to convert one
1446    boundary condition to a SWW file, ideally for tide height of 0 m
1447    (saving disk space). Then you can use field_boundary to read this SWW file
1448    and change the stage height (tide) on the fly depending on the scenario.
1449   
1450    """
1451
1452
1453    def __init__(self, filename, domain,
1454                 mean_stage=0.0,
1455                 time_thinning=1, 
1456                 use_cache=False,
1457                 verbose=False):
1458        """Constructor
1459
1460        filename: Name of sww file
1461        domain: pointer to shallow water domain for which the boundary applies
1462        mean_stage: The mean water level which will be added to stage derived
1463                    from the sww file
1464        time_thinning: Will set how many time steps from the sww file read in
1465                       will be interpolated to the boundary. For example if
1466                       the sww file has 1 second time steps and is 24 hours
1467                       in length it has 86400 time steps. If you set
1468                       time_thinning to 1 it will read all these steps.
1469                       If you set it to 100 it will read every 100th step eg
1470                       only 864 step. This parameter is very useful to increase
1471                       the speed of a model run that you are setting up
1472                       and testing.
1473        use_cache:
1474        verbose:
1475       
1476        """
1477
1478        # Create generic file_boundary object
1479        self.file_boundary = File_boundary(filename, domain,
1480                                           time_thinning=time_thinning,
1481                                           use_cache=use_cache,
1482                                           verbose=verbose)
1483       
1484        # Record information from File_boundary
1485        self.F = self.file_boundary.F
1486        self.domain = self.file_boundary.domain
1487       
1488        # Record mean stage
1489        self.mean_stage = mean_stage
1490
1491
1492    def __repr__(self):
1493        return 'Field boundary'
1494
1495
1496    def evaluate(self, vol_id=None, edge_id=None):
1497        """Return linearly interpolated values based on domain.time
1498
1499        vol_id and edge_id are ignored
1500        """
1501
1502        # Evaluate file boundary
1503        q = self.file_boundary.evaluate(vol_id, edge_id)
1504
1505        # Adjust stage
1506        for j, name in enumerate(self.domain.conserved_quantities):
1507            if name == 'stage':
1508                q[j] += self.mean_stage
1509        return q
1510
1511   
1512
1513#########################
1514#Standard forcing terms:
1515#
1516def gravity(domain):
1517    """Apply gravitational pull in the presence of bed slope
1518    """
1519
1520    xmom = domain.quantities['xmomentum'].explicit_update
1521    ymom = domain.quantities['ymomentum'].explicit_update
1522
1523    Stage = domain.quantities['stage']
1524    Elevation = domain.quantities['elevation']
1525    h = Stage.edge_values - Elevation.edge_values
1526    v = Elevation.vertex_values
1527
1528    x = domain.get_vertex_coordinates()
1529    g = domain.g
1530
1531    for k in range(len(domain)):
1532        avg_h = sum( h[k,:] )/3
1533
1534        #Compute bed slope
1535        x0, y0, x1, y1, x2, y2 = x[k,:]
1536        z0, z1, z2 = v[k,:]
1537
1538        zx, zy = gradient(x0, y0, x1, y1, x2, y2, z0, z1, z2)
1539
1540        #Update momentum
1541        xmom[k] += -g*zx*avg_h
1542        ymom[k] += -g*zy*avg_h
1543
1544
1545def gravity_c(domain):
1546    """Wrapper calling C version
1547    """
1548
1549    xmom = domain.quantities['xmomentum'].explicit_update
1550    ymom = domain.quantities['ymomentum'].explicit_update
1551
1552    stage = domain.quantities['stage']
1553    elevation = domain.quantities['elevation']
1554
1555    h = stage.centroid_values - elevation.centroid_values
1556    z = elevation.vertex_values
1557
1558    x = domain.get_vertex_coordinates()
1559    g = domain.g
1560   
1561
1562    from shallow_water_ext import gravity
1563    gravity(g, h, z, x, xmom, ymom) #, 1.0e-6)
1564
1565
1566
1567def manning_friction(domain):
1568    """Apply (Manning) friction to water momentum
1569    (Python version)
1570    """
1571
1572    from math import sqrt
1573
1574    w = domain.quantities['stage'].centroid_values
1575    z = domain.quantities['elevation'].centroid_values
1576    h = w-z
1577
1578    uh = domain.quantities['xmomentum'].centroid_values
1579    vh = domain.quantities['ymomentum'].centroid_values
1580    eta = domain.quantities['friction'].centroid_values
1581
1582    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1583    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1584
1585    N = len(domain)
1586    eps = domain.minimum_allowed_height
1587    g = domain.g
1588
1589    for k in range(N):
1590        if eta[k] >= eps:
1591            if h[k] >= eps:
1592                S = -g * eta[k]**2 * sqrt((uh[k]**2 + vh[k]**2))
1593                S /= h[k]**(7.0/3)
1594
1595                #Update momentum
1596                xmom_update[k] += S*uh[k]
1597                ymom_update[k] += S*vh[k]
1598
1599
1600def manning_friction_implicit_c(domain):
1601    """Wrapper for c version
1602    """
1603
1604
1605    #print 'Implicit friction'
1606
1607    xmom = domain.quantities['xmomentum']
1608    ymom = domain.quantities['ymomentum']
1609
1610    w = domain.quantities['stage'].centroid_values
1611    z = domain.quantities['elevation'].centroid_values
1612
1613    uh = xmom.centroid_values
1614    vh = ymom.centroid_values
1615    eta = domain.quantities['friction'].centroid_values
1616
1617    xmom_update = xmom.semi_implicit_update
1618    ymom_update = ymom.semi_implicit_update
1619
1620    N = len(domain)
1621    eps = domain.minimum_allowed_height
1622    g = domain.g
1623
1624    from shallow_water_ext import manning_friction
1625    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1626
1627
1628def manning_friction_explicit_c(domain):
1629    """Wrapper for c version
1630    """
1631
1632    #print 'Explicit friction'
1633
1634    xmom = domain.quantities['xmomentum']
1635    ymom = domain.quantities['ymomentum']
1636
1637    w = domain.quantities['stage'].centroid_values
1638    z = domain.quantities['elevation'].centroid_values
1639
1640    uh = xmom.centroid_values
1641    vh = ymom.centroid_values
1642    eta = domain.quantities['friction'].centroid_values
1643
1644    xmom_update = xmom.explicit_update
1645    ymom_update = ymom.explicit_update
1646
1647    N = len(domain)
1648    eps = domain.minimum_allowed_height
1649    g = domain.g
1650
1651    from shallow_water_ext import manning_friction
1652    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1653
1654
1655def linear_friction(domain):
1656    """Apply linear friction to water momentum
1657
1658    Assumes quantity: 'linear_friction' to be present
1659    """
1660
1661    from math import sqrt
1662
1663    w = domain.quantities['stage'].centroid_values
1664    z = domain.quantities['elevation'].centroid_values
1665    h = w-z
1666
1667    uh = domain.quantities['xmomentum'].centroid_values
1668    vh = domain.quantities['ymomentum'].centroid_values
1669    tau = domain.quantities['linear_friction'].centroid_values
1670
1671    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1672    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1673
1674    N = len(domain) # number_of_triangles
1675    eps = domain.minimum_allowed_height
1676    g = domain.g #Not necessary? Why was this added?
1677
1678    for k in range(N):
1679        if tau[k] >= eps:
1680            if h[k] >= eps:
1681                S = -tau[k]/h[k]
1682
1683                #Update momentum
1684                xmom_update[k] += S*uh[k]
1685                ymom_update[k] += S*vh[k]
1686
1687
1688
1689def check_forcefield(f):
1690    """Check that f is either
1691    1: a callable object f(t,x,y), where x and y are vectors
1692       and that it returns an array or a list of same length
1693       as x and y
1694    2: a scalar
1695    """
1696
1697    if callable(f):
1698        N = 3
1699        x = ones(3, Float)
1700        y = ones(3, Float)
1701        try:
1702            q = f(1.0, x=x, y=y)
1703        except Exception, e:
1704            msg = 'Function %s could not be executed:\n%s' %(f, e)
1705            #FIXME: Reconsider this semantics
1706            raise msg
1707
1708        try:
1709            q = array(q).astype(Float)
1710        except:
1711            msg = 'Return value from vector function %s could ' %f
1712            msg += 'not be converted into a Numeric array of floats.\n'
1713            msg += 'Specified function should return either list or array.'
1714            raise msg
1715
1716        #Is this really what we want?
1717        msg = 'Return vector from function %s ' %f
1718        msg += 'must have same lenght as input vectors'
1719        assert len(q) == N, msg
1720
1721    else:
1722        try:
1723            f = float(f)
1724        except:
1725            msg = 'Force field %s must be either a scalar' %f
1726            msg += ' or a vector function'
1727            raise Exception(msg)
1728    return f
1729
1730
1731class Wind_stress:
1732    """Apply wind stress to water momentum in terms of
1733    wind speed [m/s] and wind direction [degrees]
1734    """
1735
1736    def __init__(self, *args, **kwargs):
1737        """Initialise windfield from wind speed s [m/s]
1738        and wind direction phi [degrees]
1739
1740        Inputs v and phi can be either scalars or Python functions, e.g.
1741
1742        W = Wind_stress(10, 178)
1743
1744        #FIXME - 'normal' degrees are assumed for now, i.e. the
1745        vector (1,0) has zero degrees.
1746        We may need to convert from 'compass' degrees later on and also
1747        map from True north to grid north.
1748
1749        Arguments can also be Python functions of t,x,y as in
1750
1751        def speed(t,x,y):
1752            ...
1753            return s
1754
1755        def angle(t,x,y):
1756            ...
1757            return phi
1758
1759        where x and y are vectors.
1760
1761        and then pass the functions in
1762
1763        W = Wind_stress(speed, angle)
1764
1765        The instantiated object W can be appended to the list of
1766        forcing_terms as in
1767
1768        Alternatively, one vector valued function for (speed, angle)
1769        can be applied, providing both quantities simultaneously.
1770        As in
1771        W = Wind_stress(F), where returns (speed, angle) for each t.
1772
1773        domain.forcing_terms.append(W)
1774        """
1775
1776        from anuga.config import rho_a, rho_w, eta_w
1777        from Numeric import array, Float
1778
1779        if len(args) == 2:
1780            s = args[0]
1781            phi = args[1]
1782        elif len(args) == 1:
1783            #Assume vector function returning (s, phi)(t,x,y)
1784            vector_function = args[0]
1785            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1786            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1787        else:
1788           #Assume info is in 2 keyword arguments
1789
1790           if len(kwargs) == 2:
1791               s = kwargs['s']
1792               phi = kwargs['phi']
1793           else:
1794               raise 'Assumes two keyword arguments: s=..., phi=....'
1795
1796        self.speed = check_forcefield(s)
1797        self.phi = check_forcefield(phi)
1798
1799        self.const = eta_w*rho_a/rho_w
1800
1801
1802    def __call__(self, domain):
1803        """Evaluate windfield based on values found in domain
1804        """
1805
1806        from math import pi, cos, sin, sqrt
1807        from Numeric import Float, ones, ArrayType
1808
1809        xmom_update = domain.quantities['xmomentum'].explicit_update
1810        ymom_update = domain.quantities['ymomentum'].explicit_update
1811
1812        N = len(domain) # number_of_triangles
1813        t = domain.time
1814
1815        if callable(self.speed):
1816            xc = domain.get_centroid_coordinates()
1817            s_vec = self.speed(t, xc[:,0], xc[:,1])
1818        else:
1819            #Assume s is a scalar
1820
1821            try:
1822                s_vec = self.speed * ones(N, Float)
1823            except:
1824                msg = 'Speed must be either callable or a scalar: %s' %self.s
1825                raise msg
1826
1827
1828        if callable(self.phi):
1829            xc = domain.get_centroid_coordinates()
1830            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1831        else:
1832            #Assume phi is a scalar
1833
1834            try:
1835                phi_vec = self.phi * ones(N, Float)
1836            except:
1837                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1838                raise msg
1839
1840        assign_windfield_values(xmom_update, ymom_update,
1841                                s_vec, phi_vec, self.const)
1842
1843
1844def assign_windfield_values(xmom_update, ymom_update,
1845                            s_vec, phi_vec, const):
1846    """Python version of assigning wind field to update vectors.
1847    A c version also exists (for speed)
1848    """
1849    from math import pi, cos, sin, sqrt
1850
1851    N = len(s_vec)
1852    for k in range(N):
1853        s = s_vec[k]
1854        phi = phi_vec[k]
1855
1856        #Convert to radians
1857        phi = phi*pi/180
1858
1859        #Compute velocity vector (u, v)
1860        u = s*cos(phi)
1861        v = s*sin(phi)
1862
1863        #Compute wind stress
1864        S = const * sqrt(u**2 + v**2)
1865        xmom_update[k] += S*u
1866        ymom_update[k] += S*v
1867
1868
1869
1870class Rainfall:
1871    """Class Rainfall - general 'rain over entire domain' forcing term.
1872   
1873    Used for implementing Rainfall over the entire domain.
1874       
1875        Current Limited to only One Gauge..
1876       
1877        Need to add Spatial Varying Capability
1878        (This module came from copying and amending the Inflow Code)
1879   
1880    Rainfall(rain)
1881       
1882    rain [mm/s]:  Total rain rate over the specified domain. 
1883                  NOTE: Raingauge Data needs to reflect the time step.
1884                  IE: if Gauge is mm read at a time step, then the input
1885                  here is as mm/(timeStep) so 10mm in 5minutes becomes
1886                  10/(5x60) = 0.0333mm/s.
1887       
1888       
1889                  This parameter can be either a constant or a
1890                  function of time. Positive values indicate inflow,
1891                  negative values indicate outflow.
1892                  (and be used for Infiltration - Write Seperate Module)
1893                  The specified flow will be divided by the area of
1894                  the inflow region and then applied to update the
1895                  quantity in question.
1896   
1897    Examples
1898    How to put them in a run File...
1899       
1900    #--------------------------------------------------------------------------
1901    # Setup specialised forcing terms
1902    #--------------------------------------------------------------------------
1903    # This is the new element implemented by Ole and Rudy to allow direct
1904    # input of Inflow in mm/s
1905
1906    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms')) 
1907                        # Note need path to File in String.
1908                        # Else assumed in same directory
1909
1910    domain.forcing_terms.append(catchmentrainfall)
1911    """
1912
1913    # FIXME (OLE): Add a polygon as an alternative.
1914    # FIXME (AnyOne) : Add various methods to allow spatial variations
1915    # FIXME (OLE): Generalise to all quantities
1916
1917    def __init__(self,
1918                 rain=0.0,
1919                 quantity_name='stage'):
1920
1921        self.rain = rain
1922        self.quantity_name = quantity_name
1923   
1924    def __call__(self, domain):
1925
1926        # Update rainfall
1927        if callable(self.rain):
1928            rain = self.rain(domain.get_time())
1929        else:
1930            rain = self.rain
1931
1932        # Now rain is a number
1933        quantity = domain.quantities[self.quantity_name].explicit_update
1934        quantity[:] += rain/1000  # Converting mm/s to m/s to apply in ANUGA
1935                # 1mm of rainfall is equivalent to 1 litre /m2
1936                # Flow is expressed as m3/s converted to a stage height in (m)
1937               
1938                # Note 1m3 = 1x10^9mm3 (mls)
1939                # or is that m3 to Litres ??? Check this how is it applied !!!
1940
1941
1942class Inflow:
1943    """Class Inflow - general 'rain and drain' forcing term.
1944   
1945    Useful for implementing flows in and out of the domain.
1946   
1947    Inflow(center, radius, flow)
1948   
1949    center [m]: Coordinates at center of flow point
1950    radius [m]: Size of circular area
1951    flow [m^3/s]: Total flow rate over the specified area. 
1952                  This parameter can be either a constant or a
1953                  function of time. Positive values indicate inflow,
1954                  negative values indicate outflow.
1955                  The specified flow will be divided by the area of
1956                  the inflow region and then applied to update the
1957                  quantity in question.
1958   
1959    Examples
1960
1961    # Constant drain at 0.003 m^3/s.
1962    # The outflow area is 0.07**2*pi=0.0154 m^2
1963    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
1964    #                                     
1965    Inflow((0.7, 0.4), 0.07, -0.003)
1966
1967
1968    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
1969    # The inflow area is 0.03**2*pi = 0.00283 m^2
1970    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s     
1971    # over the specified area
1972    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
1973
1974    #--------------------------------------------------------------------------
1975    # Setup specialised forcing terms
1976    #--------------------------------------------------------------------------
1977    # This is the new element implemented by Ole to allow direct input
1978    # of Inflow in m^3/s
1979
1980    hydrograph = Inflow(center=(320, 300), radius=10,
1981                        flow=file_function('Q/QPMF_Rot_Sub13.tms'))
1982
1983    domain.forcing_terms.append(hydrograph)
1984   
1985    """
1986
1987    # FIXME (OLE): Add a polygon as an alternative.
1988    # FIXME (OLE): Generalise to all quantities
1989
1990    def __init__(self,
1991                 center=None, radius=None,
1992                 flow=0.0,
1993                 quantity_name = 'stage'):
1994
1995        from math import pi
1996
1997       
1998                 
1999        if center is not None and radius is not None:
2000            assert len(center) == 2
2001        else:
2002            msg = 'Both center and radius must be specified'
2003            raise Exception, msg
2004   
2005        self.center = center
2006        self.radius = radius
2007        self.area = radius**2*pi
2008        self.flow = flow
2009        self.quantity_name = quantity_name
2010   
2011    def __call__(self, domain):
2012
2013        # Determine indices in flow area
2014        if not hasattr(self, 'indices'):
2015            center = self.center
2016            radius = self.radius
2017           
2018            N = len(domain)   
2019            self.indices = []
2020            coordinates = domain.get_centroid_coordinates()     
2021            for k in range(N):
2022                x, y = coordinates[k,:] # Centroid
2023                if ((x-center[0])**2+(y-center[1])**2) < radius**2:
2024                    self.indices.append(k)   
2025
2026        # Update inflow
2027        if callable(self.flow):
2028            flow = self.flow(domain.get_time())
2029        else:
2030            flow = self.flow
2031
2032        # Now flow is a number
2033       
2034        quantity = domain.quantities[self.quantity_name].explicit_update
2035        for k in self.indices:
2036            quantity[k] += flow/self.area                       
2037
2038
2039#------------------
2040# Initialise module
2041#------------------
2042
2043from anuga.utilities import compile
2044if compile.can_use_C_extension('shallow_water_ext.c'):
2045    # Replace python version with c implementations
2046
2047    from shallow_water_ext import rotate, assign_windfield_values
2048    compute_fluxes = compute_fluxes_c
2049    extrapolate_second_order_sw=extrapolate_second_order_sw_c
2050    gravity = gravity_c
2051    manning_friction = manning_friction_implicit_c
2052    h_limiter = h_limiter_c
2053    balance_deep_and_shallow = balance_deep_and_shallow_c
2054    protect_against_infinitesimal_and_negative_heights =\
2055                    protect_against_infinitesimal_and_negative_heights_c
2056
2057    #distribute_to_vertices_and_edges =\
2058    #              distribute_to_vertices_and_edges_c #(like MH's)
2059
2060
2061
2062# Optimisation with psyco
2063from anuga.config import use_psyco
2064if use_psyco:
2065    try:
2066        import psyco
2067    except:
2068        import os
2069        if os.name == 'posix' and os.uname()[4] == 'x86_64':
2070            pass
2071            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2072        else:
2073            msg = 'WARNING: psyco (speedup) could not import'+\
2074                  ', you may want to consider installing it'
2075            print msg
2076    else:
2077        psyco.bind(Domain.distribute_to_vertices_and_edges)
2078        psyco.bind(Domain.compute_fluxes)
2079
2080if __name__ == "__main__":
2081    pass
2082
2083
Note: See TracBrowser for help on using the repository browser.