source: inundation/pyvolution/shallow_water.py @ 2267

Last change on this file since 2267 was 2206, checked in by steve, 19 years ago

Changed back to vpython viewer with shallow_water. Use shallow_water_vtk to get Jack's new vtk viewer

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