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

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

First stab at wind stress from file

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