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

Last change on this file since 850 was 850, checked in by ole, 20 years ago

More work towards spatio-temporal boundary.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 39.5 KB
Line 
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-02-08 06:17:25 +0000 (Tue, 08 Feb 2005) $
53#$LastChangedRevision: 850 $
54#$LastChangedBy: ole $
55
56
57from domain import *
58from region import *
59Generic_domain = Domain #Rename
60
61class Domain(Generic_domain):
62
63    def __init__(self, coordinates, vertices, boundary = None,
64                 tagged_elements = None):
65
66        conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
67        other_quantities = ['elevation', 'friction'] 
68       
69        Generic_domain.__init__(self, coordinates, vertices, boundary,
70                                conserved_quantities, other_quantities,
71                                tagged_elements)
72
73        from config import minimum_allowed_height, g
74        self.minimum_allowed_height = minimum_allowed_height
75        self.g = g
76
77        self.forcing_terms.append(gravity)
78        self.forcing_terms.append(manning_friction)
79
80        #Realtime visualisation
81        self.visualise = False
82
83        #Stored output
84        self.store = False
85        self.format = 'sww'   
86        self.smooth = True
87
88        #Reduction operation for get_vertex_values             
89        #from util import mean
90        #self.reduction = mean
91        self.reduction = min  #Looks better near steep slopes
92       
93        self.quantities_to_be_stored = ['stage']
94
95       
96        #Establish shortcuts to relevant quantities (for efficiency)
97        #self.w = self.quantities['stage']
98        #self.uh = self.quantities['xmomentum']                 
99        #self.vh = self.quantities['ymomentum']                         
100        #self.z = self.quantities['elevation']         
101        #self.eta = self.quantities['friction']                 
102
103    def check_integrity(self):
104        Generic_domain.check_integrity(self)
105
106        #Check that we are solving the shallow water wave equation
107
108        msg = 'First conserved quantity must be "stage"'
109        assert self.conserved_quantities[0] == 'stage', msg
110        msg = 'Second conserved quantity must be "xmomentum"'
111        assert self.conserved_quantities[1] == 'xmomentum', msg
112        msg = 'Third conserved quantity must be "ymomentum"'
113        assert self.conserved_quantities[2] == 'ymomentum', msg
114       
115
116    def compute_fluxes(self):
117        #Call correct module function
118        #(either from this module or C-extension)
119        compute_fluxes(self)
120
121    def distribute_to_vertices_and_edges(self):
122        #Call correct module function
123        #(either from this module or C-extension)       
124        distribute_to_vertices_and_edges(self)
125
126
127    #FIXME: Under construction   
128#     def set_defaults(self):
129#         """Set default values for uninitialised quantities.
130#         This is specific to the shallow water wave equation
131#         Defaults for 'elevation', 'friction', 'xmomentum' and 'ymomentum'
132#         are 0.0. Default for 'stage' is whatever the value of 'elevation'.
133#         """
134
135#         for name in self.other_quantities + self.conserved_quantities:
136#             print name
137#             print self.quantities.keys()
138#             if not self.quantities.has_key(name):
139#                 if name == 'stage':
140                   
141#                     if self.quantities.has_key('elevation'):
142#                         z = self.quantities['elevation'].vertex_values
143#                         self.set_quantity(name, z)
144#                     else:
145#                         self.set_quantity(name, 0.0)                       
146#                 else:   
147#                     self.set_quantity(name, 0.0)
148                   
149
150
151#         #Lift negative heights up
152#         #z = self.quantities['elevation'].vertex_values
153#         #w = self.quantities['stage'].vertex_values
154
155#         #h = w-z
156
157#         #for k in range(h.shape[0]):
158#         #    for i in range(3):
159#         #        if h[k, i] < 0.0:
160#         #            w[k, i] = z[k, i]
161
162                   
163#         #self.quantities['stage'].interpolate()
164               
165
166
167    def evolve(self, yieldstep = None, finaltime = None):
168        """Specialisation of basic evolve method from parent class
169        """
170
171        #Call check integrity here rather than from user scripts
172        #self.check_integrity()
173       
174        #Initialise real time viz if requested
175        if self.visualise is True and self.time == 0.0:
176            import realtime_visualisation as visualise
177            visualise.create_surface(self)
178
179        #Store model data, e.g. for visualisation   
180        if self.store is True and self.time == 0.0:
181            self.initialise_storage()
182            #print 'Storing results in ' + self.writer.filename
183        else:
184            #print 'Results will not be stored.'
185            #print 'To store results set domain.store = True'
186            pass
187            #FIXME: Diagnostic output should be controlled by
188            # a 'verbose' flag living in domain (or in a parent class)
189
190        #Call basic machinery from parent class
191        for t in Generic_domain.evolve(self, yieldstep, finaltime):
192            #Real time viz
193            if self.visualise is True:
194                visualise.update(self)
195
196            #Store model data, e.g. for subsequent visualisation   
197            if self.store is True:
198                #self.store_timestep(['stage', 'xmomentum', 'ymomentum'])
199                self.store_timestep(self.quantities_to_be_stored)
200               
201                #FIXME: Could maybe be taken from specified list
202                #of 'store every step' quantities       
203
204            #Pass control on to outer loop for more specific actions   
205            yield(t)
206       
207
208    def initialise_storage(self):       
209        """Create and initialise self.writer object for storing data.
210        Also, save x,y and bed elevation
211        """
212
213        import data_manager
214       
215        #Initialise writer
216        self.writer = data_manager.get_dataobject(self, mode = 'w')
217
218        #Store vertices and connectivity
219        self.writer.store_connectivity()
220
221
222    def store_timestep(self, name):
223        """Store named quantity and time.
224
225        Precondition:
226           self.write has been initialised
227        """
228        self.writer.store_timestep(name)
229       
230
231#Rotation of momentum vector
232def rotate(q, normal, direction = 1):
233    """Rotate the momentum component q (q[1], q[2])
234    from x,y coordinates to coordinates based on normal vector.
235
236    If direction is negative the rotation is inverted.
237   
238    Input vector is preserved
239
240    This function is specific to the shallow water wave equation
241    """
242
243    from Numeric import zeros, Float
244   
245    assert len(q) == 3,\
246           'Vector of conserved quantities must have length 3'\
247           'for 2D shallow water equation'
248
249    try:
250        l = len(normal)
251    except:
252        raise 'Normal vector must be an Numeric array'
253
254    assert l == 2, 'Normal vector must have 2 components'
255   
256 
257    n1 = normal[0]
258    n2 = normal[1]   
259   
260    r = zeros(len(q), Float) #Rotated quantities
261    r[0] = q[0]              #First quantity, height, is not rotated
262
263    if direction == -1:
264        n2 = -n2
265
266
267    r[1] =  n1*q[1] + n2*q[2]
268    r[2] = -n2*q[1] + n1*q[2]
269   
270    return r
271
272
273
274####################################
275# Flux computation       
276def flux_function(normal, ql, qr, zl, zr): 
277    """Compute fluxes between volumes for the shallow water wave equation
278    cast in terms of w = h+z using the 'central scheme' as described in
279
280    Kurganov, Noelle, Petrova. 'Semidiscrete Central-Upwind Schemes For
281    Hyperbolic Conservation Laws and Hamilton-Jacobi Equations'.
282    Siam J. Sci. Comput. Vol. 23, No. 3, pp. 707-740.
283
284    The implemented formula is given in equation (3.15) on page 714
285
286    Conserved quantities w, uh, vh are stored as elements 0, 1 and 2
287    in the numerical vectors ql an qr.
288
289    Bed elevations zl and zr.
290    """
291
292    from config import g, epsilon
293    from math import sqrt
294    from Numeric import array
295
296    #Align momentums with x-axis
297    q_left  = rotate(ql, normal, direction = 1)
298    q_right = rotate(qr, normal, direction = 1)
299
300    z = (zl+zr)/2 #Take average of field values
301
302    w_left  = q_left[0]   #w=h+z
303    h_left  = w_left-z
304    uh_left = q_left[1]
305
306    if h_left < epsilon:
307        u_left = 0.0  #Could have been negative
308        h_left = 0.0
309    else:   
310        u_left  = uh_left/h_left
311
312
313    w_right  = q_right[0]  #w=h+z
314    h_right  = w_right-z
315    uh_right = q_right[1]
316
317
318    if h_right < epsilon:
319        u_right = 0.0  #Could have been negative
320        h_right = 0.0
321    else:   
322        u_right  = uh_right/h_right
323
324    vh_left  = q_left[2]
325    vh_right = q_right[2]       
326
327    soundspeed_left  = sqrt(g*h_left) 
328    soundspeed_right = sqrt(g*h_right)
329
330    #Maximal wave speed
331    s_max = max(u_left+soundspeed_left, u_right+soundspeed_right, 0)
332   
333    #Minimal wave speed       
334    s_min = min(u_left-soundspeed_left, u_right-soundspeed_right, 0)
335
336    #Flux computation
337    flux_left  = array([u_left*h_left,
338                        u_left*uh_left + 0.5*g*h_left**2,
339                        u_left*vh_left])
340    flux_right = array([u_right*h_right,
341                        u_right*uh_right + 0.5*g*h_right**2,
342                        u_right*vh_right])   
343
344    denom = s_max-s_min
345    if denom == 0.0:
346        edgeflux = array([0.0, 0.0, 0.0])
347        max_speed = 0.0
348    else:   
349        edgeflux = (s_max*flux_left - s_min*flux_right)/denom
350        edgeflux += s_max*s_min*(q_right-q_left)/denom
351
352        edgeflux = rotate(edgeflux, normal, direction=-1)
353        max_speed = max(abs(s_max), abs(s_min))
354
355    return edgeflux, max_speed       
356
357
358def compute_fluxes(domain):
359    """Compute all fluxes and the timestep suitable for all volumes
360    in domain.
361
362    Compute total flux for each conserved quantity using "flux_function"
363
364    Fluxes across each edge are scaled by edgelengths and summed up
365    Resulting flux is then scaled by area and stored in
366    explicit_update for each of the three conserved quantities
367    stage, xmomentum and ymomentum   
368
369    The maximal allowable speed computed by the flux_function for each volume
370    is converted to a timestep that must not be exceeded. The minimum of
371    those is computed as the next overall timestep.
372
373    Post conditions:
374      domain.explicit_update is reset to computed flux values
375      domain.timestep is set to the largest step satisfying all volumes.
376    """
377
378    import sys
379    from Numeric import zeros, Float
380
381    N = domain.number_of_elements
382   
383    #Shortcuts
384    Stage = domain.quantities['stage']
385    Xmom = domain.quantities['xmomentum']
386    Ymom = domain.quantities['ymomentum']
387    Bed = domain.quantities['elevation']       
388
389    #Arrays
390    stage = Stage.edge_values
391    xmom =  Xmom.edge_values
392    ymom =  Ymom.edge_values
393    bed =   Bed.edge_values   
394
395    stage_bdry = Stage.boundary_values
396    xmom_bdry =  Xmom.boundary_values
397    ymom_bdry =  Ymom.boundary_values
398   
399    flux = zeros(3, Float) #Work array for summing up fluxes
400
401    #Loop
402    timestep = float(sys.maxint)   
403    for k in range(N):
404
405        flux[:] = 0.  #Reset work array
406        for i in range(3):
407            #Quantities inside volume facing neighbour i
408            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
409            zl = bed[k, i]
410
411            #Quantities at neighbour on nearest face
412            n = domain.neighbours[k,i] 
413            if n < 0:
414                m = -n-1 #Convert negative flag to index
415                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
416                zr = zl #Extend bed elevation to boundary
417            else:   
418                m = domain.neighbour_edges[k,i]
419                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
420                zr = bed[n, m]               
421
422               
423            #Outward pointing normal vector   
424            normal = domain.normals[k, 2*i:2*i+2]
425
426            #Flux computation using provided function
427            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
428            flux -= edgeflux * domain.edgelengths[k,i]
429
430            #Update optimal_timestep
431            try:
432                timestep = min(timestep, domain.radii[k]/max_speed)
433            except ZeroDivisionError:
434                pass
435
436        #Normalise by area and store for when all conserved
437        #quantities get updated
438        flux /= domain.areas[k]
439        Stage.explicit_update[k] = flux[0]
440        Xmom.explicit_update[k] = flux[1]
441        Ymom.explicit_update[k] = flux[2]
442
443   
444    domain.timestep = timestep   
445
446
447def compute_fluxes_c(domain):
448    """Wrapper calling C version of compute fluxes
449    """
450
451    import sys
452    from Numeric import zeros, Float
453
454    N = domain.number_of_elements
455   
456    #Shortcuts
457    Stage = domain.quantities['stage']
458    Xmom = domain.quantities['xmomentum']
459    Ymom = domain.quantities['ymomentum']
460    Bed = domain.quantities['elevation']       
461
462    timestep = float(sys.maxint)   
463    from shallow_water_ext import compute_fluxes
464    domain.timestep = compute_fluxes(timestep, domain.epsilon, domain.g,
465                                     domain.neighbours,
466                                     domain.neighbour_edges,
467                                     domain.normals,
468                                     domain.edgelengths,                       
469                                     domain.radii,
470                                     domain.areas,
471                                     Stage.edge_values, 
472                                     Xmom.edge_values, 
473                                     Ymom.edge_values, 
474                                     Bed.edge_values,   
475                                     Stage.boundary_values,
476                                     Xmom.boundary_values,
477                                     Ymom.boundary_values,
478                                     Stage.explicit_update,
479                                     Xmom.explicit_update,
480                                     Ymom.explicit_update)
481                                     
482
483####################################
484# Module functions for gradient limiting (distribute_to_vertices_and_edges)
485
486def distribute_to_vertices_and_edges(domain):
487    """Distribution from centroids to vertices specific to the
488    shallow water wave
489    equation.
490
491    It will ensure that h (w-z) is always non-negative even in the
492    presence of steep bed-slopes by taking a weighted average between shallow
493    and deep cases.
494   
495    In addition, all conserved quantities get distributed as per either a
496    constant (order==1) or a piecewise linear function (order==2).
497   
498    FIXME: more explanation about removal of artificial variability etc
499
500    Precondition:
501      All quantities defined at centroids and bed elevation defined at
502      vertices.
503
504    Postcondition
505      Conserved quantities defined at vertices
506   
507    """
508
509    #Remove very thin layers of water
510    protect_against_infinitesimal_and_negative_heights(domain)
511
512    #Extrapolate all conserved quantities
513    for name in domain.conserved_quantities:
514        Q = domain.quantities[name]
515        if domain.order == 1:
516            Q.extrapolate_first_order()
517        elif domain.order == 2:
518            Q.extrapolate_second_order()
519            Q.limit()                               
520        else:
521            raise 'Unknown order'
522
523    #Take bed elevation into account when water heights are small   
524    balance_deep_and_shallow(domain)
525
526    #Compute edge values by interpolation
527    for name in domain.conserved_quantities:
528        Q = domain.quantities[name]
529        Q.interpolate_from_vertices_to_edges()
530                   
531
532
533def dry(domain):
534    """Protect against infinitesimal heights and associated high velocities
535    at vertices
536    """
537
538    #FIXME: Experimental (from old version). Not in use at the moment
539   
540    #Shortcuts
541    wv = domain.quantities['stage'].vertex_values
542    zv = domain.quantities['elevation'].vertex_values
543    xmomv = domain.quantities['xmomentum'].vertex_values
544    ymomv = domain.quantities['ymomentum'].vertex_values           
545    hv = wv - zv  #Water depths at vertices
546
547    #Update
548    for k in range(domain.number_of_elements):
549        hmax = max(hv[k, :])
550       
551        if hmax < domain.minimum_allowed_height:
552            #Control stage
553            wv[k, :] = zv[k, :]
554
555            #Control momentum
556            xmomv[k,:] = ymomv[k,:] = 0.0
557   
558
559def protect_against_infinitesimal_and_negative_heights(domain):
560    """Protect against infinitesimal heights and associated high velocities
561    """
562   
563    #Shortcuts
564    wc = domain.quantities['stage'].centroid_values
565    zc = domain.quantities['elevation'].centroid_values
566    xmomc = domain.quantities['xmomentum'].centroid_values
567    ymomc = domain.quantities['ymomentum'].centroid_values           
568    hc = wc - zc  #Water depths at centroids   
569
570    #Update
571    for k in range(domain.number_of_elements):
572
573        if hc[k] < domain.minimum_allowed_height: 
574            #Control stage
575            wc[k] = zc[k]
576
577            #Control momentum
578            xmomc[k] = ymomc[k] = 0.0
579       
580
581
582def protect_against_infinitesimal_and_negative_heights_c(domain):
583    """Protect against infinitesimal heights and associated high velocities
584    """
585   
586    #Shortcuts
587    wc = domain.quantities['stage'].centroid_values
588    zc = domain.quantities['elevation'].centroid_values
589    xmomc = domain.quantities['xmomentum'].centroid_values
590    ymomc = domain.quantities['ymomentum'].centroid_values           
591
592    from shallow_water_ext import protect
593
594    protect(domain.minimum_allowed_height, wc, zc, xmomc, ymomc)
595   
596   
597def balance_deep_and_shallow(domain):
598    """Compute linear combination between stage as computed by
599    gradient-limiters and stage computed as constant height above bed.
600    The former takes precedence when heights are large compared to the
601    bed slope while the latter takes precedence when heights are
602    relatively small.  Anything in between is computed as a balanced
603    linear combination in order to avoid numerical disturbances which
604    would otherwise appear as a result of hard switching between
605    modes.
606    """
607   
608    #Shortcuts
609    wc = domain.quantities['stage'].centroid_values
610    zc = domain.quantities['elevation'].centroid_values
611    hc = wc - zc
612   
613    wv = domain.quantities['stage'].vertex_values
614    zv = domain.quantities['elevation'].vertex_values
615    hv = wv-zv
616
617
618    #Computed linear combination between constant stages and and
619    #stages parallel to the bed elevation.     
620    for k in range(domain.number_of_elements):
621        #Compute maximal variation in bed elevation
622        #  This quantitiy is
623        #    dz = max_i abs(z_i - z_c)
624        #  and it is independent of dimension
625        #  In the 1d case zc = (z0+z1)/2
626        #  In the 2d case zc = (z0+z1+z2)/3
627
628        dz = max(abs(zv[k,0]-zc[k]),
629                 abs(zv[k,1]-zc[k]),
630                 abs(zv[k,2]-zc[k]))
631
632       
633        hmin = min( hv[k,:] )
634
635       
636        #Create alpha in [0,1], where alpha==0 means using shallow
637        #first order scheme and alpha==1 means using the stage w as
638        #computed by the gradient limiter (1st or 2nd order)
639        #
640        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
641        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
642     
643        if dz > 0.0:
644            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
645        else:
646            #Flat bed
647            alpha = 1.0
648
649
650        #Weighted balance between stage parallel to bed elevation
651        #(wvi = zvi + hc) and stage as computed by 1st or 2nd
652        #order gradient limiter
653        #(wvi = zvi + hvi) where i=0,1,2 denotes the vertex ids
654        #
655        #It follows that the updated wvi is
656        #  wvi := (1-alpha)*(zvi+hc) + alpha*(zvi+hvi) =
657        #  zvi + hc + alpha*(hvi - hc)
658        #
659        #Note that hvi = zc+hc-zvi in the first order case (constant).
660
661        if alpha < 1:         
662            for i in range(3):
663                wv[k,i] = zv[k,i] + hc[k] + alpha*(hv[k,i]-hc[k])
664           
665
666            #Momentums at centroids
667            xmomc = domain.quantities['xmomentum'].centroid_values
668            ymomc = domain.quantities['ymomentum'].centroid_values       
669
670            #Momentums at vertices
671            xmomv = domain.quantities['xmomentum'].vertex_values
672            ymomv = domain.quantities['ymomentum'].vertex_values         
673
674            # Update momentum as a linear combination of
675            # xmomc and ymomc (shallow) and momentum
676            # from extrapolator xmomv and ymomv (deep).
677            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]           
678            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
679                                   
680           
681
682def balance_deep_and_shallow_c(domain):
683    """Wrapper for C implementation
684    """
685   
686    #Shortcuts
687    wc = domain.quantities['stage'].centroid_values
688    zc = domain.quantities['elevation'].centroid_values
689    hc = wc - zc
690   
691    wv = domain.quantities['stage'].vertex_values
692    zv = domain.quantities['elevation'].vertex_values
693    hv = wv-zv
694
695    #Momentums at centroids
696    xmomc = domain.quantities['xmomentum'].centroid_values
697    ymomc = domain.quantities['ymomentum'].centroid_values       
698
699    #Momentums at vertices
700    xmomv = domain.quantities['xmomentum'].vertex_values
701    ymomv = domain.quantities['ymomentum'].vertex_values         
702
703   
704
705    from shallow_water_ext import balance_deep_and_shallow
706    balance_deep_and_shallow(wc, zc, hc, wv, zv, hv,
707                             xmomc, ymomc, xmomv, ymomv)
708
709   
710
711
712###############################################
713#Boundaries - specific to the shallow water wave equation
714class Reflective_boundary(Boundary):
715    """Reflective boundary returns same conserved quantities as
716    those present in its neighbour volume but reflected.
717
718    This class is specific to the shallow water equation as it
719    works with the momentum quantities assumed to be the second
720    and third conserved quantities.
721    """
722   
723    def __init__(self, domain = None):       
724        Boundary.__init__(self)
725
726        if domain is None:
727            msg = 'Domain must be specified for reflective boundary'
728            raise msg
729       
730        #Handy shorthands
731        self.stage = domain.quantities['stage'].edge_values
732        self.xmom = domain.quantities['xmomentum'].edge_values
733        self.ymom = domain.quantities['ymomentum'].edge_values         
734        self.normals = domain.normals
735       
736        from Numeric import zeros, Float       
737        self.conserved_quantities = zeros(3, Float) 
738       
739    def __repr__(self):
740        return 'Reflective_boundary'
741
742
743    def evaluate(self, vol_id, edge_id):
744        """Reflective boundaries reverses the outward momentum
745        of the volume they serve.
746        """
747
748        q = self.conserved_quantities
749        q[0] = self.stage[vol_id, edge_id]
750        q[1] = self.xmom[vol_id, edge_id] 
751        q[2] = self.ymom[vol_id, edge_id] 
752       
753        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]     
754
755       
756        r = rotate(q, normal, direction = 1)
757        r[1] = -r[1]
758        q = rotate(r, normal, direction = -1)
759
760        return q
761
762       
763#class Spatio_temporal_boundary(Boundary):
764#    """The spatio-temporal boundary, reads values for the conserved
765#    quantities from an sww NetCDF file, and returns interpolated values
766#    at the midpoints of each associated boundaty segment.
767#    Time dependency is interpolated linearly as in util.File_function.#
768#
769#    Example:
770#    Bf = Spatio_temporal_boundary('source_file.sww', domain)
771#         
772#    """
773Spatio_temporal_boundary = File_boundary
774       
775
776       
777
778#########################
779#Standard forcing terms:
780#
781def gravity(domain):
782    """Apply gravitational pull in the presence of bed slope
783    """
784
785    from util import gradient
786    from Numeric import zeros, Float, array, sum
787
788    xmom = domain.quantities['xmomentum'].explicit_update
789    ymom = domain.quantities['ymomentum'].explicit_update
790
791    Stage = domain.quantities['stage']
792    Elevation = domain.quantities['elevation']   
793    h = Stage.edge_values - Elevation.edge_values
794    v = Elevation.vertex_values
795   
796    x = domain.get_vertex_coordinates()
797    g = domain.g
798   
799    for k in range(domain.number_of_elements):   
800        avg_h = sum( h[k,:] )/3
801       
802        #Compute bed slope
803        x0, y0, x1, y1, x2, y2 = x[k,:]   
804        z0, z1, z2 = v[k,:]
805
806        zx, zy = gradient(x0, y0, x1, y1, x2, y2, z0, z1, z2)
807
808        #Update momentum
809        xmom[k] += -g*zx*avg_h
810        ymom[k] += -g*zy*avg_h       
811
812
813def gravity_c(domain):
814    """Wrapper calling C version
815    """   
816
817    xmom = domain.quantities['xmomentum'].explicit_update
818    ymom = domain.quantities['ymomentum'].explicit_update
819
820    Stage = domain.quantities['stage']   
821    Elevation = domain.quantities['elevation']   
822    h = Stage.edge_values - Elevation.edge_values
823    v = Elevation.vertex_values
824   
825    x = domain.get_vertex_coordinates()
826    g = domain.g
827   
828   
829    from shallow_water_ext import gravity
830    gravity(g, h, v, x, xmom, ymom)
831   
832       
833def manning_friction(domain):
834    """Apply (Manning) friction to water momentum
835    """
836
837    from math import sqrt
838
839    w = domain.quantities['stage'].centroid_values
840    z = domain.quantities['elevation'].centroid_values
841    h = w-z
842   
843    uh = domain.quantities['xmomentum'].centroid_values
844    vh = domain.quantities['ymomentum'].centroid_values
845    eta = domain.quantities['friction'].centroid_values   
846   
847    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
848    ymom_update = domain.quantities['ymomentum'].semi_implicit_update   
849   
850    N = domain.number_of_elements
851    eps = domain.minimum_allowed_height
852    g = domain.g
853   
854    for k in range(N):
855        if eta[k] >= eps:
856            if h[k] >= eps:
857                S = -g * eta[k]**2 * sqrt((uh[k]**2 + vh[k]**2))
858                S /= h[k]**(7.0/3)
859
860                #Update momentum
861                xmom_update[k] += S*uh[k]
862                ymom_update[k] += S*vh[k]
863           
864       
865def manning_friction_c(domain):
866    """Wrapper for c version
867    """
868
869
870    xmom = domain.quantities['xmomentum']
871    ymom = domain.quantities['ymomentum']   
872       
873    w = domain.quantities['stage'].centroid_values
874    z = domain.quantities['elevation'].centroid_values
875   
876    uh = xmom.centroid_values
877    vh = ymom.centroid_values
878    eta = domain.quantities['friction'].centroid_values   
879   
880    xmom_update = xmom.semi_implicit_update
881    ymom_update = ymom.semi_implicit_update   
882   
883    N = domain.number_of_elements
884    eps = domain.minimum_allowed_height
885    g = domain.g
886
887    from shallow_water_ext import manning_friction
888    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
889
890
891def linear_friction(domain):
892    """Apply linear friction to water momentum
893
894    Assumes quantity: 'linear_friction' to be present
895    """
896
897    from math import sqrt
898
899    w = domain.quantities['stage'].centroid_values
900    z = domain.quantities['elevation'].centroid_values
901    h = w-z
902
903    uh = domain.quantities['xmomentum'].centroid_values
904    vh = domain.quantities['ymomentum'].centroid_values
905    tau = domain.quantities['linear_friction'].centroid_values   
906   
907    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
908    ymom_update = domain.quantities['ymomentum'].semi_implicit_update   
909   
910    N = domain.number_of_elements
911    eps = domain.minimum_allowed_height
912   
913    for k in range(N):
914        if tau[k] >= eps:
915            if h[k] >= eps:
916                S = -tau[k]/h[k]
917
918                #Update momentum
919                xmom_update[k] += S*uh[k]
920                ymom_update[k] += S*vh[k]
921           
922       
923       
924def check_forcefield(f):
925    """Check that f is either
926    1: a callable object f(t,x,y), where x and y are vectors
927       and that it returns an array or a list of same length
928       as x and y
929    2: a scalar   
930    """
931
932    from Numeric import ones, Float, array
933
934   
935    if callable(f):
936        N = 3
937        x = ones(3, Float)
938        y = ones(3, Float)           
939        try:
940            q = f(1.0, x, y)
941        except Exception, e:
942            msg = 'Function %s could not be executed:\n%s' %(f, e)
943            raise msg
944                   
945        try:
946            q = array(q).astype(Float)
947        except:
948            msg = 'Return value from vector function %s could ' %f
949            msg += 'not be converted into a Numeric array of floats.\n'
950            msg += 'Specified function should return either list or array.' 
951            raise msg
952
953        msg = 'Return vector from function %s' %f
954        msg += 'must have same lenght as input vectors'
955        assert len(q) == N, msg
956       
957    else:       
958        try:
959            f = float(f)
960        except:
961            msg = 'Force field %s must be either a scalar' %f
962            msg += ' or a vector function'
963            raise msg
964    return f   
965
966
967class Wind_stress:
968    """Apply wind stress to water momentum in terms of
969    wind speed [m/s] and wind direction [degrees]   
970    """
971
972    def __init__(self, *args, **kwargs):
973        """Initialise windfield from wind speed s [m/s]
974        and wind direction phi [degrees]
975       
976        Inputs v and phi can be either scalars or Python functions, e.g.
977
978        W = Wind_stress(10, 178)
979
980        #FIXME - 'normal' degrees are assumed for now, i.e. the
981        vector (1,0) has zero degrees.
982        We may need to convert from 'compass' degrees later on and also
983        map from True north to grid north.
984
985        Arguments can also be Python functions of t,x,y as in
986
987        def speed(t,x,y):
988            ...
989            return s
990       
991        def angle(t,x,y):
992            ...
993            return phi
994
995        where x and y are vectors.
996
997        and then pass the functions in
998
999        W = Wind_stress(speed, angle)
1000
1001        The instantiated object W can be appended to the list of
1002        forcing_terms as in
1003
1004        Alternatively, one vector valued function for (speed, angle)
1005        can be applied, providing both quantities simultaneously.
1006        As in
1007        W = Wind_stress(F), where returns (speed, angle) for each t.         
1008
1009        domain.forcing_terms.append(W)
1010        """
1011
1012        from config import rho_a, rho_w, eta_w
1013        from Numeric import array, Float               
1014
1015        if len(args) == 2:
1016            s = args[0]
1017            phi = args[1]
1018        elif len(args) == 1:
1019            #Assume vector function returning (s, phi)(t,x,y)
1020            vector_function = args[0]
1021            s = lambda t,x,y: vector_function(t,x,y)[0]
1022            phi = lambda t,x,y: vector_function(t,x,y)[1]
1023        else:
1024           #Assume info is in 2 keyword arguments
1025       
1026           if len(kwargs) == 2:
1027               s = kwargs['s']
1028               phi = kwargs['phi']
1029           else:
1030               raise 'Assumes two keyword arguments: s=..., phi=....'   
1031           
1032        self.speed = check_forcefield(s)
1033        self.phi = check_forcefield(phi)
1034           
1035        self.const = eta_w*rho_a/rho_w
1036       
1037
1038    def __call__(self, domain):
1039        """Evaluate windfield based on values found in domain
1040        """
1041
1042        from math import pi, cos, sin, sqrt
1043        from Numeric import Float, ones, ArrayType
1044       
1045        xmom_update = domain.quantities['xmomentum'].explicit_update
1046        ymom_update = domain.quantities['ymomentum'].explicit_update   
1047       
1048        N = domain.number_of_elements
1049        t = domain.time       
1050       
1051        if callable(self.speed):
1052            xc = domain.get_centroid_coordinates()           
1053            s_vec = self.speed(t, xc[:,0], xc[:,1])
1054        else:
1055            #Assume s is a scalar
1056
1057            try:
1058                s_vec = self.speed * ones(N, Float)
1059            except:
1060                msg = 'Speed must be either callable or a scalar: %s' %self.s
1061                raise msg
1062
1063
1064        if callable(self.phi):
1065            xc = domain.get_centroid_coordinates()           
1066            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1067        else:
1068            #Assume phi is a scalar
1069
1070            try:
1071                phi_vec = self.phi * ones(N, Float)           
1072            except:
1073                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1074                raise msg
1075
1076        assign_windfield_values(xmom_update, ymom_update,
1077                                s_vec, phi_vec, self.const)
1078
1079
1080def assign_windfield_values(xmom_update, ymom_update,
1081                            s_vec, phi_vec, const):
1082    """Python version of assigning wind field to update vectors.
1083    A c version also exists (for speed)
1084    """
1085    from math import pi, cos, sin, sqrt
1086   
1087    N = len(s_vec)
1088    for k in range(N):
1089        s = s_vec[k]
1090        phi = phi_vec[k]
1091
1092        #Convert to radians
1093        phi = phi*pi/180
1094
1095        #Compute velocity vector (u, v)
1096        u = s*cos(phi)
1097        v = s*sin(phi)
1098       
1099        #Compute wind stress
1100        S = const * sqrt(u**2 + v**2)
1101        xmom_update[k] += S*u
1102        ymom_update[k] += S*v       
1103           
1104
1105
1106###########################
1107###########################
1108#Geometries
1109
1110
1111#FIXME: Rethink this way of creating values.
1112
1113
1114class Weir:
1115    """Set a bathymetry for weir with a hole and a downstream gutter
1116    x,y are assumed to be in the unit square
1117    """
1118   
1119    def __init__(self, stage):
1120        self.inflow_stage = stage
1121
1122    def __call__(self, x, y):   
1123        from Numeric import zeros, Float
1124        from math import sqrt
1125   
1126        N = len(x)
1127        assert N == len(y)   
1128
1129        z = zeros(N, Float)
1130        for i in range(N):
1131            z[i] = -x[i]/2  #General slope
1132
1133            #Flattish bit to the left
1134            if x[i] < 0.3:
1135                z[i] = -x[i]/10 
1136           
1137            #Weir
1138            if x[i] >= 0.3 and x[i] < 0.4:
1139                z[i] = -x[i]+0.9
1140
1141            #Dip
1142            x0 = 0.6
1143            #depth = -1.3
1144            depth = -1.0
1145            #plateaux = -0.9
1146            plateaux = -0.6           
1147            if y[i] < 0.7:
1148                if x[i] > x0 and x[i] < 0.9:
1149                    z[i] = depth
1150
1151                #RHS plateaux
1152                if x[i] >= 0.9:
1153                    z[i] = plateaux
1154                   
1155                   
1156            elif y[i] >= 0.7 and y[i] < 1.5:
1157                #Restrict and deepen
1158                if x[i] >= x0 and x[i] < 0.8:
1159                    z[i] = depth-(y[i]/3-0.3)
1160                    #z[i] = depth-y[i]/5
1161                    #z[i] = depth
1162                elif x[i] >= 0.8:   
1163                    #RHS plateaux
1164                    z[i] = plateaux
1165                   
1166            elif y[i] >= 1.5:
1167                if x[i] >= x0 and x[i] < 0.8 + (y[i]-1.5)/1.2:
1168                    #Widen up and stay at constant depth
1169                    z[i] = depth-1.5/5
1170                elif x[i] >= 0.8 + (y[i]-1.5)/1.2:                       
1171                    #RHS plateaux
1172                    z[i] = plateaux                                   
1173                   
1174
1175            #Hole in weir (slightly higher than inflow condition)
1176            if x[i] >= 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1177                z[i] = -x[i]+self.inflow_stage + 0.02
1178
1179            #Channel behind weir
1180            x0 = 0.5
1181            if x[i] >= 0.4 and x[i] < x0 and y[i] > 0.2 and y[i] < 0.4:
1182                z[i] = -x[i]+self.inflow_stage + 0.02
1183               
1184            if x[i] >= x0 and x[i] < 0.6 and y[i] > 0.2 and y[i] < 0.4:
1185                #Flatten it out towards the end
1186                z[i] = -x0+self.inflow_stage + 0.02 + (x0-x[i])/5
1187
1188            #Hole to the east
1189            x0 = 1.1; y0 = 0.35
1190            #if x[i] < -0.2 and y < 0.5:
1191            if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1192                z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-1.0
1193
1194            #Tiny channel draining hole
1195            if x[i] >= 1.14 and x[i] < 1.2 and y[i] >= 0.4 and y[i] < 0.6:
1196                z[i] = -0.9 #North south
1197               
1198            if x[i] >= 0.9 and x[i] < 1.18 and y[i] >= 0.58 and y[i] < 0.65:
1199                z[i] = -1.0 + (x[i]-0.9)/3 #East west
1200           
1201           
1202
1203            #Stuff not in use
1204           
1205            #Upward slope at inlet to the north west
1206            #if x[i] < 0.0: # and y[i] > 0.5:
1207            #    #z[i] = -y[i]+0.5  #-x[i]/2
1208            #    z[i] = x[i]/4 - y[i]**2 + 0.5
1209
1210            #Hole to the west
1211            #x0 = -0.4; y0 = 0.35 # center
1212            #if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1213            #    z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-0.2
1214
1215
1216
1217
1218
1219        return z/2
1220
1221class Weir_simple:
1222    """Set a bathymetry for weir with a hole and a downstream gutter
1223    x,y are assumed to be in the unit square
1224    """
1225   
1226    def __init__(self, stage):
1227        self.inflow_stage = stage
1228
1229    def __call__(self, x, y):   
1230        from Numeric import zeros, Float
1231   
1232        N = len(x)
1233        assert N == len(y)   
1234
1235        z = zeros(N, Float)
1236        for i in range(N):
1237            z[i] = -x[i]  #General slope
1238
1239            #Flat bit to the left
1240            if x[i] < 0.3:
1241                z[i] = -x[i]/10  #General slope
1242           
1243            #Weir
1244            if x[i] > 0.3 and x[i] < 0.4:
1245                z[i] = -x[i]+0.9
1246
1247            #Dip
1248            if x[i] > 0.6 and x[i] < 0.9:
1249                z[i] = -x[i]-0.5  #-y[i]/5
1250
1251            #Hole in weir (slightly higher than inflow condition)
1252            if x[i] > 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1253                z[i] = -x[i]+self.inflow_stage + 0.05       
1254           
1255
1256        return z/2
1257       
1258
1259           
1260class Constant_height:
1261    """Set an initial condition with constant water height, e.g
1262    stage s = z+h
1263    """
1264    def __init__(self, W, h):
1265        self.W = W
1266        self.h = h
1267
1268    def __call__(self, x, y):
1269        if self.W is None:
1270            from Numeric import ones, Float
1271            return self.h*ones(len(x), Float)
1272        else:
1273            return self.W(x,y) + self.h
1274
1275
1276
1277##############################################
1278#Initialise module
1279
1280
1281import compile
1282if compile.can_use_C_extension('shallow_water_ext.c'):
1283    #Replace python version with c implementations
1284   
1285    from shallow_water_ext import rotate, assign_windfield_values
1286    compute_fluxes = compute_fluxes_c
1287    gravity = gravity_c
1288    manning_friction = manning_friction_c
1289    balance_deep_and_shallow = balance_deep_and_shallow_c
1290    protect_against_infinitesimal_and_negative_heights = protect_against_infinitesimal_and_negative_heights_c
1291
1292   
1293    #distribute_to_vertices_and_edges = distribute_to_vertices_and_edges_c
1294
1295
1296#Optimisation with psyco
1297from config import use_psyco
1298if use_psyco:
1299    try:
1300        import psyco
1301    except:
1302        msg = 'WARNING: psyco (speedup) could not import'+\
1303              ', you may want to consider installing it'
1304        print msg
1305    else:
1306        psyco.bind(Domain.distribute_to_vertices_and_edges)
1307        psyco.bind(Domain.compute_fluxes)
1308       
1309if __name__ == "__main__":
1310    pass
Note: See TracBrowser for help on using the repository browser.