source: inundation/ga/storm_surge/pyvolution/shallow_water.py @ 1454

Last change on this file since 1454 was 1387, checked in by steve, 20 years ago

Need to look at uint test for inscribed circle

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 49.5 KB
RevLine 
[1265]1"""Class Domain -
22D triangular domains for finite-volume computations of
3the shallow water wave equation.
4
5This module contains a specialisation of class Domain from module domain.py
6consisting of methods specific to the Shallow Water Wave Equation
7
8
9U_t + E_x + G_y = S
10
11where
12
13U = [w, uh, vh]
14E = [uh, u^2h + gh^2/2, uvh]
15G = [vh, uvh, v^2h + gh^2/2]
16S represents source terms forcing the system
17(e.g. gravity, friction, wind stress, ...)
18
19and _t, _x, _y denote the derivative with respect to t, x and y respectively.
20
21The quantities are
22
23symbol    variable name    explanation
24x         x                horizontal distance from origin [m]
25y         y                vertical distance from origin [m]
26z         elevation        elevation of bed on which flow is modelled [m]
27h         height           water height above z [m]
28w         stage            absolute water level, w = z+h [m]
29u                          speed in the x direction [m/s]
30v                          speed in the y direction [m/s]
31uh        xmomentum        momentum in the x direction [m^2/s]
32vh        ymomentum        momentum in the y direction [m^2/s]
33
34eta                        mannings friction coefficient [to appear]
35nu                         wind stress coefficient [to appear]
36
37The conserved quantities are w, uh, vh
38
39
40For details see e.g.
41Christopher Zoppou and Stephen Roberts,
42Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
43Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
44
45
46Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
47Geoscience Australia, 2004
48"""
49
50#Subversion keywords:
51#
52#$LastChangedDate: 2005-05-13 08:15:08 +0000 (Fri, 13 May 2005) $
53#$LastChangedRevision: 1387 $
54#$LastChangedBy: ole $
55
56
57from domain import *
58from region import *
59Generic_domain = Domain #Rename
60
61#Shalow water domain
62class Domain(Generic_domain):
63
64    def __init__(self, coordinates, vertices, boundary = None,
[1360]65                 tagged_elements = None, geo_reference = None,
66                 use_inscribed_circle=False):
[1265]67
68        conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
69        other_quantities = ['elevation', 'friction']
70        Generic_domain.__init__(self, coordinates, vertices, boundary,
71                                conserved_quantities, other_quantities,
[1360]72                                tagged_elements, geo_reference, use_inscribed_circle)
[1265]73
74        from config import minimum_allowed_height, g
75        self.minimum_allowed_height = minimum_allowed_height
76        self.g = g
77
78        self.forcing_terms.append(gravity)
79        self.forcing_terms.append(manning_friction)
80
81        #Realtime visualisation
82        self.visualise = False
[1295]83        self.visualise_color_stage = False
84        self.visualise_stage_range = 1.0
[1363]85        self.visualise_timer = True
86        self.visualise_range_z = None
[1265]87
88        #Stored output
89        self.store = False
90        self.format = 'sww'
91        self.smooth = True
92
93        #Reduction operation for get_vertex_values
94        #from util import mean
95        #self.reduction = mean
96        self.reduction = min  #Looks better near steep slopes
97
98        self.quantities_to_be_stored = ['stage']
99
100
101        #Establish shortcuts to relevant quantities (for efficiency)
102        #self.w = self.quantities['stage']
103        #self.uh = self.quantities['xmomentum']
104        #self.vh = self.quantities['ymomentum']
105        #self.z = self.quantities['elevation']
106        #self.eta = self.quantities['friction']
107
108    def check_integrity(self):
109        Generic_domain.check_integrity(self)
110
111        #Check that we are solving the shallow water wave equation
112
113        msg = 'First conserved quantity must be "stage"'
114        assert self.conserved_quantities[0] == 'stage', msg
115        msg = 'Second conserved quantity must be "xmomentum"'
116        assert self.conserved_quantities[1] == 'xmomentum', msg
117        msg = 'Third conserved quantity must be "ymomentum"'
118        assert self.conserved_quantities[2] == 'ymomentum', msg
119
120
121    def compute_fluxes(self):
122        #Call correct module function
123        #(either from this module or C-extension)
124        compute_fluxes(self)
125
126    def distribute_to_vertices_and_edges(self):
127        #Call correct module function
128        #(either from this module or C-extension)
129        distribute_to_vertices_and_edges(self)
130
131
132    #FIXME: Under construction
133#     def set_defaults(self):
134#         """Set default values for uninitialised quantities.
135#         This is specific to the shallow water wave equation
136#         Defaults for 'elevation', 'friction', 'xmomentum' and 'ymomentum'
137#         are 0.0. Default for 'stage' is whatever the value of 'elevation'.
138#         """
139
140#         for name in self.other_quantities + self.conserved_quantities:
141#             print name
142#             print self.quantities.keys()
143#             if not self.quantities.has_key(name):
144#                 if name == 'stage':
145
146#                     if self.quantities.has_key('elevation'):
147#                         z = self.quantities['elevation'].vertex_values
148#                         self.set_quantity(name, z)
149#                     else:
150#                         self.set_quantity(name, 0.0)
151#                 else:
152#                     self.set_quantity(name, 0.0)
153
154
155
156#         #Lift negative heights up
157#         #z = self.quantities['elevation'].vertex_values
158#         #w = self.quantities['stage'].vertex_values
159
160#         #h = w-z
161
162#         #for k in range(h.shape[0]):
163#         #    for i in range(3):
164#         #        if h[k, i] < 0.0:
165#         #            w[k, i] = z[k, i]
166
167
168#         #self.quantities['stage'].interpolate()
169
170
171
172    def evolve(self, yieldstep = None, finaltime = None):
173        """Specialisation of basic evolve method from parent class
174        """
175
176        #Call check integrity here rather than from user scripts
177        #self.check_integrity()
178
179        msg = 'Parameter beta_h must be in the interval [0, 1['
180        assert 0 <= self.beta_h < 1.0, msg
181        msg = 'Parameter beta_w must be in the interval [0, 1['
182        assert 0 <= self.beta_w < 1.0, msg
183
184
185        #Initial update of vertex and edge values before any storage
186        #and or visualisation
187        self.distribute_to_vertices_and_edges()
188
189
190        #Initialise real time viz if requested
191        if self.visualise is True and self.time == 0.0:
192            import realtime_visualisation_new as visualise
193            visualise.create_surface(self)
194
195        #Store model data, e.g. for visualisation
196        if self.store is True and self.time == 0.0:
197            self.initialise_storage()
198            #print 'Storing results in ' + self.writer.filename
199        else:
200            #print 'Results will not be stored.'
201            #print 'To store results set domain.store = True'
202            pass
203            #FIXME: Diagnostic output should be controlled by
204            # a 'verbose' flag living in domain (or in a parent class)
205
206        #Call basic machinery from parent class
207        for t in Generic_domain.evolve(self, yieldstep, finaltime):
208            #Real time viz
209            if self.visualise is True:
210                visualise.update(self)
211
212            #Store model data, e.g. for subsequent visualisation
213            if self.store is True:
214                self.store_timestep(self.quantities_to_be_stored)
215
216                #FIXME: Could maybe be taken from specified list
217                #of 'store every step' quantities
218
219            #Pass control on to outer loop for more specific actions
220            yield(t)
221
222
223    def initialise_storage(self):
224        """Create and initialise self.writer object for storing data.
225        Also, save x,y and bed elevation
226        """
227
228        import data_manager
229
230        #Initialise writer
231        self.writer = data_manager.get_dataobject(self, mode = 'w')
232
233        #Store vertices and connectivity
234        self.writer.store_connectivity()
235
236
237    def store_timestep(self, name):
238        """Store named quantity and time.
239
240        Precondition:
241           self.write has been initialised
242        """
243        self.writer.store_timestep(name)
244
245
246#Rotation of momentum vector
247def rotate(q, normal, direction = 1):
248    """Rotate the momentum component q (q[1], q[2])
249    from x,y coordinates to coordinates based on normal vector.
250
251    If direction is negative the rotation is inverted.
252
253    Input vector is preserved
254
255    This function is specific to the shallow water wave equation
256    """
257
258    from Numeric import zeros, Float
259
260    assert len(q) == 3,\
261           'Vector of conserved quantities must have length 3'\
262           'for 2D shallow water equation'
263
264    try:
265        l = len(normal)
266    except:
267        raise 'Normal vector must be an Numeric array'
268
269    assert l == 2, 'Normal vector must have 2 components'
270
271
272    n1 = normal[0]
273    n2 = normal[1]
274
275    r = zeros(len(q), Float) #Rotated quantities
276    r[0] = q[0]              #First quantity, height, is not rotated
277
278    if direction == -1:
279        n2 = -n2
280
281
282    r[1] =  n1*q[1] + n2*q[2]
283    r[2] = -n2*q[1] + n1*q[2]
284
285    return r
286
287
288
289####################################
290# Flux computation
291def flux_function(normal, ql, qr, zl, zr):
292    """Compute fluxes between volumes for the shallow water wave equation
293    cast in terms of w = h+z using the 'central scheme' as described in
294
295    Kurganov, Noelle, Petrova. 'Semidiscrete Central-Upwind Schemes For
296    Hyperbolic Conservation Laws and Hamilton-Jacobi Equations'.
297    Siam J. Sci. Comput. Vol. 23, No. 3, pp. 707-740.
298
299    The implemented formula is given in equation (3.15) on page 714
300
301    Conserved quantities w, uh, vh are stored as elements 0, 1 and 2
302    in the numerical vectors ql an qr.
303
304    Bed elevations zl and zr.
305    """
306
307    from config import g, epsilon
308    from math import sqrt
309    from Numeric import array
310
311    #Align momentums with x-axis
312    q_left  = rotate(ql, normal, direction = 1)
313    q_right = rotate(qr, normal, direction = 1)
314
315    z = (zl+zr)/2 #Take average of field values
316
317    w_left  = q_left[0]   #w=h+z
318    h_left  = w_left-z
319    uh_left = q_left[1]
320
321    if h_left < epsilon:
322        u_left = 0.0  #Could have been negative
323        h_left = 0.0
324    else:
325        u_left  = uh_left/h_left
326
327
328    w_right  = q_right[0]  #w=h+z
329    h_right  = w_right-z
330    uh_right = q_right[1]
331
332
333    if h_right < epsilon:
334        u_right = 0.0  #Could have been negative
335        h_right = 0.0
336    else:
337        u_right  = uh_right/h_right
338
339    vh_left  = q_left[2]
340    vh_right = q_right[2]
341
342    soundspeed_left  = sqrt(g*h_left)
343    soundspeed_right = sqrt(g*h_right)
344
345    #Maximal wave speed
346    s_max = max(u_left+soundspeed_left, u_right+soundspeed_right, 0)
347
348    #Minimal wave speed
349    s_min = min(u_left-soundspeed_left, u_right-soundspeed_right, 0)
350
351    #Flux computation
352
353    #FIXME(Ole): Why is it again that we don't
354    #use uh_left and uh_right directly in the first entries?
355    flux_left  = array([u_left*h_left,
356                        u_left*uh_left + 0.5*g*h_left**2,
357                        u_left*vh_left])
358    flux_right = array([u_right*h_right,
359                        u_right*uh_right + 0.5*g*h_right**2,
360                        u_right*vh_right])
361
362    denom = s_max-s_min
363    if denom == 0.0:
364        edgeflux = array([0.0, 0.0, 0.0])
365        max_speed = 0.0
366    else:
367        edgeflux = (s_max*flux_left - s_min*flux_right)/denom
368        edgeflux += s_max*s_min*(q_right-q_left)/denom
369
370        edgeflux = rotate(edgeflux, normal, direction=-1)
371        max_speed = max(abs(s_max), abs(s_min))
372
373    return edgeflux, max_speed
374
375
376def compute_fluxes(domain):
377    """Compute all fluxes and the timestep suitable for all volumes
378    in domain.
379
380    Compute total flux for each conserved quantity using "flux_function"
381
382    Fluxes across each edge are scaled by edgelengths and summed up
383    Resulting flux is then scaled by area and stored in
384    explicit_update for each of the three conserved quantities
385    stage, xmomentum and ymomentum
386
387    The maximal allowable speed computed by the flux_function for each volume
388    is converted to a timestep that must not be exceeded. The minimum of
389    those is computed as the next overall timestep.
390
391    Post conditions:
392      domain.explicit_update is reset to computed flux values
393      domain.timestep is set to the largest step satisfying all volumes.
394    """
395
396    import sys
397    from Numeric import zeros, Float
398
399    N = domain.number_of_elements
400
401    #Shortcuts
402    Stage = domain.quantities['stage']
403    Xmom = domain.quantities['xmomentum']
404    Ymom = domain.quantities['ymomentum']
405    Bed = domain.quantities['elevation']
406
407    #Arrays
408    stage = Stage.edge_values
409    xmom =  Xmom.edge_values
410    ymom =  Ymom.edge_values
411    bed =   Bed.edge_values
412
413    stage_bdry = Stage.boundary_values
414    xmom_bdry =  Xmom.boundary_values
415    ymom_bdry =  Ymom.boundary_values
416
417    flux = zeros(3, Float) #Work array for summing up fluxes
418
419    #Loop
420    timestep = float(sys.maxint)
421    for k in range(N):
422
423        flux[:] = 0.  #Reset work array
424        for i in range(3):
425            #Quantities inside volume facing neighbour i
426            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
427            zl = bed[k, i]
428
429            #Quantities at neighbour on nearest face
430            n = domain.neighbours[k,i]
431            if n < 0:
432                m = -n-1 #Convert negative flag to index
433                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
434                zr = zl #Extend bed elevation to boundary
435            else:
436                m = domain.neighbour_edges[k,i]
437                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
438                zr = bed[n, m]
439
440
441            #Outward pointing normal vector
442            normal = domain.normals[k, 2*i:2*i+2]
443
444            #Flux computation using provided function
445            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
446            flux -= edgeflux * domain.edgelengths[k,i]
447
448            #Update optimal_timestep
449            try:
450                timestep = min(timestep, 0.5*domain.radii[k]/max_speed)
451            except ZeroDivisionError:
452                pass
453
454        #Normalise by area and store for when all conserved
455        #quantities get updated
456        flux /= domain.areas[k]
457        Stage.explicit_update[k] = flux[0]
458        Xmom.explicit_update[k] = flux[1]
459        Ymom.explicit_update[k] = flux[2]
460
461
462    domain.timestep = timestep
463
464
465def compute_fluxes_c(domain):
466    """Wrapper calling C version of compute fluxes
467    """
468
469    import sys
470    from Numeric import zeros, Float
471
472    N = domain.number_of_elements
473
474    #Shortcuts
475    Stage = domain.quantities['stage']
476    Xmom = domain.quantities['xmomentum']
477    Ymom = domain.quantities['ymomentum']
478    Bed = domain.quantities['elevation']
479
480    timestep = float(sys.maxint)
481    from shallow_water_ext import compute_fluxes
482    domain.timestep = compute_fluxes(timestep, domain.epsilon, domain.g,
483                                     domain.neighbours,
484                                     domain.neighbour_edges,
485                                     domain.normals,
486                                     domain.edgelengths,
487                                     domain.radii,
488                                     domain.areas,
489                                     Stage.edge_values,
490                                     Xmom.edge_values,
491                                     Ymom.edge_values,
492                                     Bed.edge_values,
493                                     Stage.boundary_values,
494                                     Xmom.boundary_values,
495                                     Ymom.boundary_values,
496                                     Stage.explicit_update,
497                                     Xmom.explicit_update,
498                                     Ymom.explicit_update)
499
500
501####################################
502# Module functions for gradient limiting (distribute_to_vertices_and_edges)
503
504def distribute_to_vertices_and_edges(domain):
505    """Distribution from centroids to vertices specific to the
506    shallow water wave
507    equation.
508
509    It will ensure that h (w-z) is always non-negative even in the
510    presence of steep bed-slopes by taking a weighted average between shallow
511    and deep cases.
512
513    In addition, all conserved quantities get distributed as per either a
514    constant (order==1) or a piecewise linear function (order==2).
515
516    FIXME: more explanation about removal of artificial variability etc
517
518    Precondition:
519      All quantities defined at centroids and bed elevation defined at
520      vertices.
521
522    Postcondition
523      Conserved quantities defined at vertices
524
525    """
526
527    #Remove very thin layers of water
528    protect_against_infinitesimal_and_negative_heights(domain)
529
530    #Extrapolate all conserved quantities
531    for name in domain.conserved_quantities:
532        Q = domain.quantities[name]
533        if domain.order == 1:
534            Q.extrapolate_first_order()
535        elif domain.order == 2:
536            Q.extrapolate_second_order()
537            Q.limit()
538        else:
539            raise 'Unknown order'
540
541    #Take bed elevation into account when water heights are small
542    balance_deep_and_shallow(domain)
543
544    #Compute edge values by interpolation
545    for name in domain.conserved_quantities:
546        Q = domain.quantities[name]
547        Q.interpolate_from_vertices_to_edges()
548
549
550
551def dry(domain):
552    """Protect against infinitesimal heights and associated high velocities
553    at vertices
554    """
555
556    #FIXME: Experimental (from old version). Not in use at the moment
557
558    #Shortcuts
559    wv = domain.quantities['stage'].vertex_values
560    zv = domain.quantities['elevation'].vertex_values
561    xmomv = domain.quantities['xmomentum'].vertex_values
562    ymomv = domain.quantities['ymomentum'].vertex_values
563    hv = wv - zv  #Water depths at vertices
564
565    #Update
566    for k in range(domain.number_of_elements):
567        hmax = max(hv[k, :])
568
569        if hmax < domain.minimum_allowed_height:
570            #Control stage
571            wv[k, :] = zv[k, :]
572
573            #Control momentum
574            xmomv[k,:] = ymomv[k,:] = 0.0
575
576
577def protect_against_infinitesimal_and_negative_heights(domain):
578    """Protect against infinitesimal heights and associated high velocities
579    """
580
581    #FIXME: Look here for error
582
583    #Shortcuts
584    wc = domain.quantities['stage'].centroid_values
585    zc = domain.quantities['elevation'].centroid_values
586    xmomc = domain.quantities['xmomentum'].centroid_values
587    ymomc = domain.quantities['ymomentum'].centroid_values
588    hc = wc - zc  #Water depths at centroids
589
590    #print zc
591    #print '1', wc
592    #Update
593    for k in range(domain.number_of_elements):
594
595        if hc[k] < domain.minimum_allowed_height:
596            #Control stage
597            wc[k] = zc[k]
598
599            #Control momentum
600            xmomc[k] = ymomc[k] = 0.0
601
602    #print '2', wc
603
604
605def protect_against_infinitesimal_and_negative_heights_c(domain):
606    """Protect against infinitesimal heights and associated high velocities
607    """
608
609    #Shortcuts
610    wc = domain.quantities['stage'].centroid_values
611    zc = domain.quantities['elevation'].centroid_values
612    xmomc = domain.quantities['xmomentum'].centroid_values
613    ymomc = domain.quantities['ymomentum'].centroid_values
614
615    from shallow_water_ext import protect
616
617    protect(domain.minimum_allowed_height, wc, zc, xmomc, ymomc)
618
619
620
621def h_limiter(domain):
622    """Limit slopes for each volume to eliminate artificial variance
623    introduced by e.g. second order extrapolator
624
625    limit on h = w-z
626
627    This limiter depends on two quantities (w,z) so it resides within
628    this module rather than within quantity.py
629    """
630
631    from Numeric import zeros, Float
632
633    N = domain.number_of_elements
634    beta_h = domain.beta_h
635
636    #Shortcuts
637    wc = domain.quantities['stage'].centroid_values
638    zc = domain.quantities['elevation'].centroid_values
639    hc = wc - zc
640
641    wv = domain.quantities['stage'].vertex_values
642    zv = domain.quantities['elevation'].vertex_values
643    hv = wv-zv
644
645    hvbar = zeros(hv.shape, Float) #h-limited values
646
647    #Find min and max of this and neighbour's centroid values
648    hmax = zeros(hc.shape, Float)
649    hmin = zeros(hc.shape, Float)
650
651    for k in range(N):
652        hmax[k] = hmin[k] = hc[k]
653        for i in range(3):
654            n = domain.neighbours[k,i]
655            if n >= 0:
656                hn = hc[n] #Neighbour's centroid value
657
658                hmin[k] = min(hmin[k], hn)
659                hmax[k] = max(hmax[k], hn)
660
661
662    #Diffences between centroids and maxima/minima
663    dhmax = hmax - hc
664    dhmin = hmin - hc
665
666    #Deltas between vertex and centroid values
667    dh = zeros(hv.shape, Float)
668    for i in range(3):
669        dh[:,i] = hv[:,i] - hc
670
671    #Phi limiter
672    for k in range(N):
673
674        #Find the gradient limiter (phi) across vertices
675        phi = 1.0
676        for i in range(3):
677            r = 1.0
678            if (dh[k,i] > 0): r = dhmax[k]/dh[k,i]
679            if (dh[k,i] < 0): r = dhmin[k]/dh[k,i]
680
681            phi = min( min(r*beta_h, 1), phi )
682
683        #Then update using phi limiter
684        for i in range(3):
685            hvbar[k,i] = hc[k] + phi*dh[k,i]
686
687    return hvbar
688
689
690
691def h_limiter_c(domain):
692    """Limit slopes for each volume to eliminate artificial variance
693    introduced by e.g. second order extrapolator
694
695    limit on h = w-z
696
697    This limiter depends on two quantities (w,z) so it resides within
698    this module rather than within quantity.py
699
700    Wrapper for c-extension
701    """
702
703    from Numeric import zeros, Float
704
705    N = domain.number_of_elements
706    beta_h = domain.beta_h
707
708    #Shortcuts
709    wc = domain.quantities['stage'].centroid_values
710    zc = domain.quantities['elevation'].centroid_values
711    hc = wc - zc
712
713    wv = domain.quantities['stage'].vertex_values
714    zv = domain.quantities['elevation'].vertex_values
715    hv = wv - zv
716
717    #Call C-extension
718    from shallow_water_ext import h_limiter
719    hvbar = h_limiter(domain, hc, hv)
720
721    return hvbar
722
723
724def balance_deep_and_shallow(domain):
725    """Compute linear combination between stage as computed by
726    gradient-limiters limiting using w, and stage computed as
727    constant height above bed and limited using h.
728    The former takes precedence when heights are large compared to the
729    bed slope while the latter takes precedence when heights are
730    relatively small.  Anything in between is computed as a balanced
731    linear combination in order to avoid numerical disturbances which
732    would otherwise appear as a result of hard switching between
733    modes.
734
735    The h-limiter is always applied irrespective of the order.
736    """
737
738    #New idea.
739    #
740    # In the presence and near of bedslope it is necessary to
741    # limit slopes based on differences in h rather than w
742    # (which is what is needed away from the bed).
743    #
744    # So whether extrapolation was first order or second order,
745    # it will need to be balanced with a h-limited gradient.
746    #
747    # For this we will use the quantity alpha as before
748    #
749
750    #Shortcuts
751    wc = domain.quantities['stage'].centroid_values
752    zc = domain.quantities['elevation'].centroid_values
753    hc = wc - zc
754
755    wv = domain.quantities['stage'].vertex_values
756    zv = domain.quantities['elevation'].vertex_values
757    hv = wv-zv
758
759    #Limit h
760    hvbar = h_limiter(domain)
761
762    for k in range(domain.number_of_elements):
763        #Compute maximal variation in bed elevation
764        #  This quantitiy is
765        #    dz = max_i abs(z_i - z_c)
766        #  and it is independent of dimension
767        #  In the 1d case zc = (z0+z1)/2
768        #  In the 2d case zc = (z0+z1+z2)/3
769
770        dz = max(abs(zv[k,0]-zc[k]),
771                 abs(zv[k,1]-zc[k]),
772                 abs(zv[k,2]-zc[k]))
773
774
775        hmin = min( hv[k,:] )
776
777        #Create alpha in [0,1], where alpha==0 means using the h-limited
778        #stage and alpha==1 means using the w-limited stage as
779        #computed by the gradient limiter (both 1st or 2nd order)
780
781        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
782        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
783
784        if dz > 0.0:
785            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
786        else:
787            #Flat bed
788            alpha = 1.0
789
790        #Let
791        #
792        #  wvi be the w-limited stage (wvi = zvi + hvi)
793        #  wvi- be the h-limited state (wvi- = zvi + hvi-)
794        #
795        #
796        #where i=0,1,2 denotes the vertex ids
797        #
798        #Weighted balance between w-limited and h-limited stage is
799        #
800        #  wvi := (1-alpha)*(zvi+hvi-) + alpha*(zvi+hvi)
801        #
802        #It follows that the updated wvi is
803        #  wvi := zvi + (1-alpha)*hvi- + alpha*hvi
804        #
805        # Momentum is balanced between constant and limited
806
807
808        #for i in range(3):
809        #    wv[k,i] = zv[k,i] + hvbar[k,i]
810
811        #return
812
813        if alpha < 1:
814
815            for i in range(3):
816                wv[k,i] = zv[k,i] + (1-alpha)*hvbar[k,i] + alpha*hv[k,i]
817
818            #Momentums at centroids
819            xmomc = domain.quantities['xmomentum'].centroid_values
820            ymomc = domain.quantities['ymomentum'].centroid_values
821
822            #Momentums at vertices
823            xmomv = domain.quantities['xmomentum'].vertex_values
824            ymomv = domain.quantities['ymomentum'].vertex_values
825
826            # Update momentum as a linear combination of
827            # xmomc and ymomc (shallow) and momentum
828            # from extrapolator xmomv and ymomv (deep).
829            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
830            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
831
832
833def balance_deep_and_shallow_c(domain):
834    """Wrapper for C implementation
835    """
836
837    #Shortcuts
838    wc = domain.quantities['stage'].centroid_values
839    zc = domain.quantities['elevation'].centroid_values
840    hc = wc - zc
841
842    wv = domain.quantities['stage'].vertex_values
843    zv = domain.quantities['elevation'].vertex_values
844    hv = wv - zv
845
846    #Momentums at centroids
847    xmomc = domain.quantities['xmomentum'].centroid_values
848    ymomc = domain.quantities['ymomentum'].centroid_values
849
850    #Momentums at vertices
851    xmomv = domain.quantities['xmomentum'].vertex_values
852    ymomv = domain.quantities['ymomentum'].vertex_values
853
854    #Limit h
855    hvbar = h_limiter(domain)
856
857    #This is how one would make a first order h_limited value
858    #as in the old balancer (pre 17 Feb 2005):
859    #from Numeric import zeros, Float
860    #hvbar = zeros( (len(hc), 3), Float)
861    #for i in range(3):
862    #    hvbar[:,i] = hc[:]
863
864    from shallow_water_ext import balance_deep_and_shallow
865    balance_deep_and_shallow(wc, zc, hc, wv, zv, hv, hvbar,
866                             xmomc, ymomc, xmomv, ymomv)
867
868
869
870
871###############################################
872#Boundaries - specific to the shallow water wave equation
873class Reflective_boundary(Boundary):
874    """Reflective boundary returns same conserved quantities as
875    those present in its neighbour volume but reflected.
876
877    This class is specific to the shallow water equation as it
878    works with the momentum quantities assumed to be the second
879    and third conserved quantities.
880    """
881
882    def __init__(self, domain = None):
883        Boundary.__init__(self)
884
885        if domain is None:
886            msg = 'Domain must be specified for reflective boundary'
887            raise msg
888
889        #Handy shorthands
890        self.stage = domain.quantities['stage'].edge_values
891        self.xmom = domain.quantities['xmomentum'].edge_values
892        self.ymom = domain.quantities['ymomentum'].edge_values
893        self.normals = domain.normals
894
895        from Numeric import zeros, Float
896        self.conserved_quantities = zeros(3, Float)
897
898    def __repr__(self):
899        return 'Reflective_boundary'
900
901
902    def evaluate(self, vol_id, edge_id):
903        """Reflective boundaries reverses the outward momentum
904        of the volume they serve.
905        """
906
907        q = self.conserved_quantities
908        q[0] = self.stage[vol_id, edge_id]
909        q[1] = self.xmom[vol_id, edge_id]
910        q[2] = self.ymom[vol_id, edge_id]
911
912        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
913
914
915        r = rotate(q, normal, direction = 1)
916        r[1] = -r[1]
917        q = rotate(r, normal, direction = -1)
918
919        return q
920
921
922#class Spatio_temporal_boundary(Boundary):
923#    """The spatio-temporal boundary, reads values for the conserved
924#    quantities from an sww NetCDF file, and returns interpolated values
925#    at the midpoints of each associated boundaty segment.
926#    Time dependency is interpolated linearly as in util.File_function.#
927#
928#    Example:
929#    Bf = Spatio_temporal_boundary('source_file.sww', domain)
930#
931#    """
932Spatio_temporal_boundary = File_boundary
933
934
935
936
937#########################
938#Standard forcing terms:
939#
940def gravity(domain):
941    """Apply gravitational pull in the presence of bed slope
942    """
943
944    from util import gradient
945    from Numeric import zeros, Float, array, sum
946
947    xmom = domain.quantities['xmomentum'].explicit_update
948    ymom = domain.quantities['ymomentum'].explicit_update
949
950    Stage = domain.quantities['stage']
951    Elevation = domain.quantities['elevation']
952    h = Stage.edge_values - Elevation.edge_values
953    v = Elevation.vertex_values
954
955    x = domain.get_vertex_coordinates()
956    g = domain.g
957
958    for k in range(domain.number_of_elements):
959        avg_h = sum( h[k,:] )/3
960
961        #Compute bed slope
962        x0, y0, x1, y1, x2, y2 = x[k,:]
963        z0, z1, z2 = v[k,:]
964
965        zx, zy = gradient(x0, y0, x1, y1, x2, y2, z0, z1, z2)
966
967        #Update momentum
968        xmom[k] += -g*zx*avg_h
969        ymom[k] += -g*zy*avg_h
970
971
972def gravity_c(domain):
973    """Wrapper calling C version
974    """
975
976    xmom = domain.quantities['xmomentum'].explicit_update
977    ymom = domain.quantities['ymomentum'].explicit_update
978
979    Stage = domain.quantities['stage']
980    Elevation = domain.quantities['elevation']
981    h = Stage.edge_values - Elevation.edge_values
982    v = Elevation.vertex_values
983
984    x = domain.get_vertex_coordinates()
985    g = domain.g
986
987
988    from shallow_water_ext import gravity
989    gravity(g, h, v, x, xmom, ymom)
990
991
992def manning_friction(domain):
993    """Apply (Manning) friction to water momentum
994    """
995
996    from math import sqrt
997
998    w = domain.quantities['stage'].centroid_values
999    z = domain.quantities['elevation'].centroid_values
1000    h = w-z
1001
1002    uh = domain.quantities['xmomentum'].centroid_values
1003    vh = domain.quantities['ymomentum'].centroid_values
1004    eta = domain.quantities['friction'].centroid_values
1005
1006    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1007    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1008
1009    N = domain.number_of_elements
1010    eps = domain.minimum_allowed_height
1011    g = domain.g
1012
1013    for k in range(N):
1014        if eta[k] >= eps:
1015            if h[k] >= eps:
1016                S = -g * eta[k]**2 * sqrt((uh[k]**2 + vh[k]**2))
1017                S /= h[k]**(7.0/3)
1018
1019                #Update momentum
1020                xmom_update[k] += S*uh[k]
1021                ymom_update[k] += S*vh[k]
1022
1023
1024def manning_friction_c(domain):
1025    """Wrapper for c version
1026    """
1027
1028
1029    xmom = domain.quantities['xmomentum']
1030    ymom = domain.quantities['ymomentum']
1031
1032    w = domain.quantities['stage'].centroid_values
1033    z = domain.quantities['elevation'].centroid_values
1034
1035    uh = xmom.centroid_values
1036    vh = ymom.centroid_values
1037    eta = domain.quantities['friction'].centroid_values
1038
1039    xmom_update = xmom.semi_implicit_update
1040    ymom_update = ymom.semi_implicit_update
1041
1042    N = domain.number_of_elements
1043    eps = domain.minimum_allowed_height
1044    g = domain.g
1045
1046    from shallow_water_ext import manning_friction
1047    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1048
1049
1050def linear_friction(domain):
1051    """Apply linear friction to water momentum
1052
1053    Assumes quantity: 'linear_friction' to be present
1054    """
1055
1056    from math import sqrt
1057
1058    w = domain.quantities['stage'].centroid_values
1059    z = domain.quantities['elevation'].centroid_values
1060    h = w-z
1061
1062    uh = domain.quantities['xmomentum'].centroid_values
1063    vh = domain.quantities['ymomentum'].centroid_values
1064    tau = domain.quantities['linear_friction'].centroid_values
1065
1066    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1067    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1068
1069    N = domain.number_of_elements
1070    eps = domain.minimum_allowed_height
1071    g = domain.g #Not necessary? Why was this added?
1072
1073    for k in range(N):
1074        if tau[k] >= eps:
1075            if h[k] >= eps:
1076                S = -tau[k]/h[k]
1077
1078                #Update momentum
1079                xmom_update[k] += S*uh[k]
1080                ymom_update[k] += S*vh[k]
1081
1082
1083
1084def check_forcefield(f):
1085    """Check that f is either
1086    1: a callable object f(t,x,y), where x and y are vectors
1087       and that it returns an array or a list of same length
1088       as x and y
1089    2: a scalar
1090    """
1091
1092    from Numeric import ones, Float, array
1093
1094
1095    if callable(f):
1096        N = 3
1097        x = ones(3, Float)
1098        y = ones(3, Float)
1099        try:
1100            q = f(1.0, x, y)
1101        except Exception, e:
1102            msg = 'Function %s could not be executed:\n%s' %(f, e)
1103            #FIXME: Reconsider this semantics
1104            raise msg
1105
1106        try:
1107            q = array(q).astype(Float)
1108        except:
1109            msg = 'Return value from vector function %s could ' %f
1110            msg += 'not be converted into a Numeric array of floats.\n'
1111            msg += 'Specified function should return either list or array.'
1112            raise msg
1113
1114        msg = 'Return vector from function %s' %f
1115        msg += 'must have same lenght as input vectors'
1116        assert len(q) == N, msg
1117
1118    else:
1119        try:
1120            f = float(f)
1121        except:
1122            msg = 'Force field %s must be either a scalar' %f
1123            msg += ' or a vector function'
1124            raise msg
1125    return f
1126
1127
1128class Wind_stress:
1129    """Apply wind stress to water momentum in terms of
1130    wind speed [m/s] and wind direction [degrees]
1131    """
1132
1133    def __init__(self, *args, **kwargs):
1134        """Initialise windfield from wind speed s [m/s]
1135        and wind direction phi [degrees]
1136
1137        Inputs v and phi can be either scalars or Python functions, e.g.
1138
1139        W = Wind_stress(10, 178)
1140
1141        #FIXME - 'normal' degrees are assumed for now, i.e. the
1142        vector (1,0) has zero degrees.
1143        We may need to convert from 'compass' degrees later on and also
1144        map from True north to grid north.
1145
1146        Arguments can also be Python functions of t,x,y as in
1147
1148        def speed(t,x,y):
1149            ...
1150            return s
1151
1152        def angle(t,x,y):
1153            ...
1154            return phi
1155
1156        where x and y are vectors.
1157
1158        and then pass the functions in
1159
1160        W = Wind_stress(speed, angle)
1161
1162        The instantiated object W can be appended to the list of
1163        forcing_terms as in
1164
1165        Alternatively, one vector valued function for (speed, angle)
1166        can be applied, providing both quantities simultaneously.
1167        As in
1168        W = Wind_stress(F), where returns (speed, angle) for each t.
1169
1170        domain.forcing_terms.append(W)
1171        """
1172
1173        from config import rho_a, rho_w, eta_w
1174        from Numeric import array, Float
1175
1176        if len(args) == 2:
1177            s = args[0]
1178            phi = args[1]
1179        elif len(args) == 1:
1180            #Assume vector function returning (s, phi)(t,x,y)
1181            vector_function = args[0]
1182            s = lambda t,x,y: vector_function(t,x,y)[0]
1183            phi = lambda t,x,y: vector_function(t,x,y)[1]
1184        else:
1185           #Assume info is in 2 keyword arguments
1186
1187           if len(kwargs) == 2:
1188               s = kwargs['s']
1189               phi = kwargs['phi']
1190           else:
1191               raise 'Assumes two keyword arguments: s=..., phi=....'
1192
1193        self.speed = check_forcefield(s)
1194        self.phi = check_forcefield(phi)
1195
1196        self.const = eta_w*rho_a/rho_w
1197
1198
1199    def __call__(self, domain):
1200        """Evaluate windfield based on values found in domain
1201        """
1202
1203        from math import pi, cos, sin, sqrt
1204        from Numeric import Float, ones, ArrayType
1205
1206        xmom_update = domain.quantities['xmomentum'].explicit_update
1207        ymom_update = domain.quantities['ymomentum'].explicit_update
1208
1209        N = domain.number_of_elements
1210        t = domain.time
1211
1212        if callable(self.speed):
1213            xc = domain.get_centroid_coordinates()
1214            s_vec = self.speed(t, xc[:,0], xc[:,1])
1215        else:
1216            #Assume s is a scalar
1217
1218            try:
1219                s_vec = self.speed * ones(N, Float)
1220            except:
1221                msg = 'Speed must be either callable or a scalar: %s' %self.s
1222                raise msg
1223
1224
1225        if callable(self.phi):
1226            xc = domain.get_centroid_coordinates()
1227            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1228        else:
1229            #Assume phi is a scalar
1230
1231            try:
1232                phi_vec = self.phi * ones(N, Float)
1233            except:
1234                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1235                raise msg
1236
1237        assign_windfield_values(xmom_update, ymom_update,
1238                                s_vec, phi_vec, self.const)
1239
1240
1241def assign_windfield_values(xmom_update, ymom_update,
1242                            s_vec, phi_vec, const):
1243    """Python version of assigning wind field to update vectors.
1244    A c version also exists (for speed)
1245    """
1246    from math import pi, cos, sin, sqrt
1247
1248    N = len(s_vec)
1249    for k in range(N):
1250        s = s_vec[k]
1251        phi = phi_vec[k]
1252
1253        #Convert to radians
1254        phi = phi*pi/180
1255
1256        #Compute velocity vector (u, v)
1257        u = s*cos(phi)
1258        v = s*sin(phi)
1259
1260        #Compute wind stress
1261        S = const * sqrt(u**2 + v**2)
1262        xmom_update[k] += S*u
1263        ymom_update[k] += S*v
1264
1265
1266
1267##############################
1268#OBSOLETE STUFF
1269
1270def balance_deep_and_shallow_old(domain):
1271    """Compute linear combination between stage as computed by
1272    gradient-limiters and stage computed as constant height above bed.
1273    The former takes precedence when heights are large compared to the
1274    bed slope while the latter takes precedence when heights are
1275    relatively small.  Anything in between is computed as a balanced
1276    linear combination in order to avoid numerical disturbances which
1277    would otherwise appear as a result of hard switching between
1278    modes.
1279    """
1280
1281    #OBSOLETE
1282
1283    #Shortcuts
1284    wc = domain.quantities['stage'].centroid_values
1285    zc = domain.quantities['elevation'].centroid_values
1286    hc = wc - zc
1287
1288    wv = domain.quantities['stage'].vertex_values
1289    zv = domain.quantities['elevation'].vertex_values
1290    hv = wv-zv
1291
1292
1293    #Computed linear combination between constant stages and and
1294    #stages parallel to the bed elevation.
1295    for k in range(domain.number_of_elements):
1296        #Compute maximal variation in bed elevation
1297        #  This quantitiy is
1298        #    dz = max_i abs(z_i - z_c)
1299        #  and it is independent of dimension
1300        #  In the 1d case zc = (z0+z1)/2
1301        #  In the 2d case zc = (z0+z1+z2)/3
1302
1303        dz = max(abs(zv[k,0]-zc[k]),
1304                 abs(zv[k,1]-zc[k]),
1305                 abs(zv[k,2]-zc[k]))
1306
1307
1308        hmin = min( hv[k,:] )
1309
1310        #Create alpha in [0,1], where alpha==0 means using shallow
1311        #first order scheme and alpha==1 means using the stage w as
1312        #computed by the gradient limiter (1st or 2nd order)
1313        #
1314        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
1315        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
1316
1317        if dz > 0.0:
1318            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
1319        else:
1320            #Flat bed
1321            alpha = 1.0
1322
1323        #Weighted balance between stage parallel to bed elevation
1324        #(wvi = zvi + hc) and stage as computed by 1st or 2nd
1325        #order gradient limiter
1326        #(wvi = zvi + hvi) where i=0,1,2 denotes the vertex ids
1327        #
1328        #It follows that the updated wvi is
1329        #  wvi := (1-alpha)*(zvi+hc) + alpha*(zvi+hvi) =
1330        #  zvi + hc + alpha*(hvi - hc)
1331        #
1332        #Note that hvi = zc+hc-zvi in the first order case (constant).
1333
1334        if alpha < 1:
1335            for i in range(3):
1336                wv[k,i] = zv[k,i] + hc[k] + alpha*(hv[k,i]-hc[k])
1337
1338
1339            #Momentums at centroids
1340            xmomc = domain.quantities['xmomentum'].centroid_values
1341            ymomc = domain.quantities['ymomentum'].centroid_values
1342
1343            #Momentums at vertices
1344            xmomv = domain.quantities['xmomentum'].vertex_values
1345            ymomv = domain.quantities['ymomentum'].vertex_values
1346
1347            # Update momentum as a linear combination of
1348            # xmomc and ymomc (shallow) and momentum
1349            # from extrapolator xmomv and ymomv (deep).
1350            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
1351            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
1352
1353
1354
1355###########################
1356###########################
1357#Geometries
1358
1359
1360#FIXME: Rethink this way of creating values.
1361
1362
1363class Weir:
1364    """Set a bathymetry for weir with a hole and a downstream gutter
1365    x,y are assumed to be in the unit square
1366    """
1367
1368    def __init__(self, stage):
1369        self.inflow_stage = stage
1370
1371    def __call__(self, x, y):
1372        from Numeric import zeros, Float
1373        from math import sqrt
1374
1375        N = len(x)
1376        assert N == len(y)
1377
1378        z = zeros(N, Float)
1379        for i in range(N):
1380            z[i] = -x[i]/2  #General slope
1381
1382            #Flattish bit to the left
1383            if x[i] < 0.3:
1384                z[i] = -x[i]/10
1385
1386            #Weir
1387            if x[i] >= 0.3 and x[i] < 0.4:
1388                z[i] = -x[i]+0.9
1389
1390            #Dip
1391            x0 = 0.6
1392            #depth = -1.3
1393            depth = -1.0
1394            #plateaux = -0.9
1395            plateaux = -0.6
1396            if y[i] < 0.7:
1397                if x[i] > x0 and x[i] < 0.9:
1398                    z[i] = depth
1399
1400                #RHS plateaux
1401                if x[i] >= 0.9:
1402                    z[i] = plateaux
1403
1404
1405            elif y[i] >= 0.7 and y[i] < 1.5:
1406                #Restrict and deepen
1407                if x[i] >= x0 and x[i] < 0.8:
1408                    z[i] = depth-(y[i]/3-0.3)
1409                    #z[i] = depth-y[i]/5
1410                    #z[i] = depth
1411                elif x[i] >= 0.8:
1412                    #RHS plateaux
1413                    z[i] = plateaux
1414
1415            elif y[i] >= 1.5:
1416                if x[i] >= x0 and x[i] < 0.8 + (y[i]-1.5)/1.2:
1417                    #Widen up and stay at constant depth
1418                    z[i] = depth-1.5/5
1419                elif x[i] >= 0.8 + (y[i]-1.5)/1.2:
1420                    #RHS plateaux
1421                    z[i] = plateaux
1422
1423
1424            #Hole in weir (slightly higher than inflow condition)
1425            if x[i] >= 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1426                z[i] = -x[i]+self.inflow_stage + 0.02
1427
1428            #Channel behind weir
1429            x0 = 0.5
1430            if x[i] >= 0.4 and x[i] < x0 and y[i] > 0.2 and y[i] < 0.4:
1431                z[i] = -x[i]+self.inflow_stage + 0.02
1432
1433            if x[i] >= x0 and x[i] < 0.6 and y[i] > 0.2 and y[i] < 0.4:
1434                #Flatten it out towards the end
1435                z[i] = -x0+self.inflow_stage + 0.02 + (x0-x[i])/5
1436
1437            #Hole to the east
1438            x0 = 1.1; y0 = 0.35
1439            #if x[i] < -0.2 and y < 0.5:
1440            if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1441                z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-1.0
1442
1443            #Tiny channel draining hole
1444            if x[i] >= 1.14 and x[i] < 1.2 and y[i] >= 0.4 and y[i] < 0.6:
1445                z[i] = -0.9 #North south
1446
1447            if x[i] >= 0.9 and x[i] < 1.18 and y[i] >= 0.58 and y[i] < 0.65:
1448                z[i] = -1.0 + (x[i]-0.9)/3 #East west
1449
1450
1451
1452            #Stuff not in use
1453
1454            #Upward slope at inlet to the north west
1455            #if x[i] < 0.0: # and y[i] > 0.5:
1456            #    #z[i] = -y[i]+0.5  #-x[i]/2
1457            #    z[i] = x[i]/4 - y[i]**2 + 0.5
1458
1459            #Hole to the west
1460            #x0 = -0.4; y0 = 0.35 # center
1461            #if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1462            #    z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-0.2
1463
1464
1465
1466
1467
1468        return z/2
1469
1470class Weir_simple:
1471    """Set a bathymetry for weir with a hole and a downstream gutter
1472    x,y are assumed to be in the unit square
1473    """
1474
1475    def __init__(self, stage):
1476        self.inflow_stage = stage
1477
1478    def __call__(self, x, y):
1479        from Numeric import zeros, Float
1480
1481        N = len(x)
1482        assert N == len(y)
1483
1484        z = zeros(N, Float)
1485        for i in range(N):
1486            z[i] = -x[i]  #General slope
1487
1488            #Flat bit to the left
1489            if x[i] < 0.3:
1490                z[i] = -x[i]/10  #General slope
1491
1492            #Weir
1493            if x[i] > 0.3 and x[i] < 0.4:
1494                z[i] = -x[i]+0.9
1495
1496            #Dip
1497            if x[i] > 0.6 and x[i] < 0.9:
1498                z[i] = -x[i]-0.5  #-y[i]/5
1499
1500            #Hole in weir (slightly higher than inflow condition)
1501            if x[i] > 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1502                z[i] = -x[i]+self.inflow_stage + 0.05
1503
1504
1505        return z/2
1506
1507
1508
[1387]1509class Constant_stage:
1510    """Set an initial condition with constant stage
1511    """
1512    def __init__(self, s):
1513        self.s = s
1514
1515    def __call__(self, x, y):
1516        return self.s
1517
[1265]1518class Constant_height:
1519    """Set an initial condition with constant water height, e.g
1520    stage s = z+h
1521    """
[1387]1522
[1265]1523    def __init__(self, W, h):
1524        self.W = W
1525        self.h = h
1526
1527    def __call__(self, x, y):
1528        if self.W is None:
1529            from Numeric import ones, Float
1530            return self.h*ones(len(x), Float)
1531        else:
1532            return self.W(x,y) + self.h
1533
1534
1535
1536
1537def compute_fluxes_python(domain):
1538    """Compute all fluxes and the timestep suitable for all volumes
1539    in domain.
1540
1541    Compute total flux for each conserved quantity using "flux_function"
1542
1543    Fluxes across each edge are scaled by edgelengths and summed up
1544    Resulting flux is then scaled by area and stored in
1545    explicit_update for each of the three conserved quantities
1546    stage, xmomentum and ymomentum
1547
1548    The maximal allowable speed computed by the flux_function for each volume
1549    is converted to a timestep that must not be exceeded. The minimum of
1550    those is computed as the next overall timestep.
1551
1552    Post conditions:
1553      domain.explicit_update is reset to computed flux values
1554      domain.timestep is set to the largest step satisfying all volumes.
1555    """
1556
1557    import sys
1558    from Numeric import zeros, Float
1559
1560    N = domain.number_of_elements
1561
1562    #Shortcuts
1563    Stage = domain.quantities['stage']
1564    Xmom = domain.quantities['xmomentum']
1565    Ymom = domain.quantities['ymomentum']
1566    Bed = domain.quantities['elevation']
1567
1568    #Arrays
1569    stage = Stage.edge_values
1570    xmom =  Xmom.edge_values
1571    ymom =  Ymom.edge_values
1572    bed =   Bed.edge_values
1573
1574    stage_bdry = Stage.boundary_values
1575    xmom_bdry =  Xmom.boundary_values
1576    ymom_bdry =  Ymom.boundary_values
1577
1578    flux = zeros((N,3), Float) #Work array for summing up fluxes
1579
1580    #Loop
1581    timestep = float(sys.maxint)
1582    for k in range(N):
1583
1584        for i in range(3):
1585            #Quantities inside volume facing neighbour i
1586            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
1587            zl = bed[k, i]
1588
1589            #Quantities at neighbour on nearest face
1590            n = domain.neighbours[k,i]
1591            if n < 0:
1592                m = -n-1 #Convert negative flag to index
1593                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
1594                zr = zl #Extend bed elevation to boundary
1595            else:
1596                m = domain.neighbour_edges[k,i]
1597                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
1598                zr = bed[n, m]
1599
1600
1601            #Outward pointing normal vector
1602            normal = domain.normals[k, 2*i:2*i+2]
1603
1604            #Flux computation using provided function
1605            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
1606
1607            flux[k,:] = edgeflux
1608
1609    return flux
1610
1611
1612
1613
1614
1615
1616
1617##############################################
1618#Initialise module
1619
1620
1621import compile
1622if compile.can_use_C_extension('shallow_water_ext.c'):
1623    #Replace python version with c implementations
1624
1625    from shallow_water_ext import rotate, assign_windfield_values
1626    compute_fluxes = compute_fluxes_c
1627    gravity = gravity_c
1628    manning_friction = manning_friction_c
1629    h_limiter = h_limiter_c
1630    balance_deep_and_shallow = balance_deep_and_shallow_c
1631    protect_against_infinitesimal_and_negative_heights = protect_against_infinitesimal_and_negative_heights_c
1632
1633
1634    #distribute_to_vertices_and_edges = distribute_to_vertices_and_edges_c
1635
1636
1637
1638#Optimisation with psyco
1639from config import use_psyco
1640if use_psyco:
1641    try:
1642        import psyco
1643    except:
1644        import os
1645        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1646            pass
1647            #Psyco isn't supported on 64 bit systems, but it doesn't matter
1648        else:
1649            msg = 'WARNING: psyco (speedup) could not import'+\
1650                  ', you may want to consider installing it'
1651            print msg
1652    else:
1653        psyco.bind(Domain.distribute_to_vertices_and_edges)
1654        psyco.bind(Domain.compute_fluxes)
1655
1656if __name__ == "__main__":
1657    pass
Note: See TracBrowser for help on using the repository browser.