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

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

More cleanup of triangle and node formats + better comments

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