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

Last change on this file since 4254 was 4254, checked in by duncan, 17 years ago

the point file blocking line size can now be changed in domain

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