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

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

Played with wind stress + c-extension

File size: 42.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
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       
898def check_forcefield(f):
899    """Check that f is either
900    1: a callable object of x,y,t, where x and y are vectors
901       and that it returns an array or a list of same length
902       as x and y
903    2: a scalar   
904    """
905
906    from Numeric import ones, Float, array
907
908   
909    if callable(f):
910        N = 3
911        x = ones(3, Float)
912        y = ones(3, Float)           
913        try:
914            q = f(x, y, 1.0)
915        except Exception, e:
916            msg = 'Function %s could not be executed:\n%s' %(f, e)
917            raise msg
918                   
919        try:
920            q = array(q).astype(Float)
921        except:
922            msg = 'Return value from vector function %s could ' %f
923            msg += 'not be converted into a Numeric array of floats.\n'
924            msg += 'Specified function should return either list or array.' 
925            raise msg
926
927        msg = 'Return vector from function %s' %f
928        msg += 'must have same lenght as input vectors'
929        assert len(q) == N, msg
930       
931    else:       
932        try:
933            f = float(f)
934        except:
935            msg = 'Force field %s must be either a scalar' %f
936            msg += ' or a vector function'
937            raise msg
938    return f   
939
940
941class Wind_stress:
942    """Apply wind stress to water momentum
943    """
944
945    def __init__(self, s, phi):
946        """Initialise windfield from wind speed s [m/s]
947        and wind direction phi [degrees]
948       
949        Inputs v and phi can be either scalars or Python functions, e.g.
950
951        W = Wind_stress(10, 178)
952
953        #FIXME - 'normal' degrees are assumed for now, i.e. the
954        vector (1,0) has zero degrees.
955        We may need to convert from 'compass' degrees later on and also
956        map from True north to grid north.
957
958        Arguments can also be Python functions of x,y,t as in
959
960        def speed(x,y,t):
961            ...
962            return s
963       
964        def angle(x,y,t):
965            ...
966            return phi
967
968        where x and y are vectors.
969
970        and then pass the functions in
971
972        W = Wind_stress(speed, angle)
973
974        The instantiated object W can be appended to the list of
975        forcing_terms as in
976
977        domain.forcing_terms.append(W)
978       
979        """
980
981        from config import rho_a, rho_w, eta_w
982        from Numeric import array, Float               
983
984        self.speed = check_forcefield(s)
985        self.phi = check_forcefield(phi)
986
987        self.const = eta_w*rho_a/rho_w
988       
989
990    def __call__(self, domain):
991        """Evaluate windfield based on values found in domain
992        """
993
994        from math import pi, cos, sin, sqrt
995        from Numeric import Float, ones, ArrayType
996       
997        xmom_update = domain.quantities['xmomentum'].explicit_update
998        ymom_update = domain.quantities['ymomentum'].explicit_update   
999       
1000        N = domain.number_of_elements
1001        t = domain.time       
1002       
1003        if callable(self.speed):
1004            xc = domain.get_centroid_coordinates()           
1005            s_vec = self.speed(xc[:,0], xc[:,1], t)
1006        else:
1007            #Assume s is a scalar
1008
1009            try:
1010                s_vec = self.speed * ones(N, Float)
1011            except:
1012                msg = 'Speed must be either callable or a scalar: %s' %self.s
1013                raise msg
1014
1015
1016        if callable(self.phi):
1017            xc = domain.get_centroid_coordinates()           
1018            phi_vec = self.phi(xc[:,0], xc[:,1], t)
1019        else:
1020            #Assume phi is a scalar
1021
1022            try:
1023                phi_vec = self.phi * ones(N, Float)           
1024            except:
1025                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1026                raise msg
1027
1028        assign_windfield_values(xmom_update, ymom_update,
1029                                s_vec, phi_vec, self.const)
1030
1031
1032def assign_windfield_values(xmom_update, ymom_update,
1033                            s_vec, phi_vec, const):
1034    """Python version of assigning wind field to update vectors.
1035    A c version also exists (for speed)
1036    """
1037    from math import pi, cos, sin, sqrt
1038   
1039    N = len(s_vec)
1040    for k in range(N):
1041        s = s_vec[k]
1042        phi = phi_vec[k]
1043
1044        #Convert to radians
1045        phi = phi*pi/180
1046
1047        #Compute velocity vector (u, v)
1048        u = s*cos(phi)
1049        v = s*sin(phi)
1050       
1051        #Compute wind stress
1052        S = const * sqrt(u**2 + v**2)
1053        xmom_update[k] += S*u
1054        ymom_update[k] += S*v       
1055           
1056
1057class Wind_stress_from_file:
1058    """Apply wind stress read from a file to water momentum
1059
1060    At this stage the wind field is assumed to depend on time only, i.e
1061    no spatial dependency.
1062   
1063    FIXME: This class may be incorporated in the generic Wind_stress class
1064    Superseded
1065    """
1066
1067   
1068    def __init__(self, filename):
1069        """Initialise windfield from a file with the following format
1070
1071        time [DD/MM/YY hh:mm:ss], speed [m/s] direction [degrees]
1072       
1073        e.g.
1074       
1075        03/09/04 19:15:00, 9.53 39
1076       
1077        domain.forcing_terms.append(W)
1078       
1079        """
1080
1081        import time
1082        from Numeric import array
1083        from config import time_format
1084
1085
1086        from config import rho_a, rho_w, eta_w
1087        self.const = eta_w*rho_a/rho_w
1088       
1089
1090        try:
1091            fid = open(filename)
1092        except Exception, e:
1093            msg = 'Wind stress file %s could not be opened: %s\n'\
1094                  %(filename, e)
1095            raise msg
1096
1097
1098        line = fid.readline()
1099        fid.close()
1100        fields = line.split(',')
1101        msg = 'File %s must have the format date, values'
1102        assert len(fields) == 2, msg
1103
1104        try:
1105            starttime = time.mktime(time.strptime(fields[0], time_format))
1106        except ValueError:   
1107            msg = 'First field in file %s must be' %filename
1108            msg += ' date-time with format %s.\n' %time_format
1109            msg += 'I got %s instead.' %fields[0] 
1110            raise msg
1111
1112        #Split values
1113        values = []
1114        for value in fields[1].split():
1115            values.append(float(value))
1116
1117        q = array(values)
1118       
1119        msg = 'ERROR: File function must return a 1d list or array '
1120        assert len(q.shape) == 1, msg
1121           
1122
1123        msg = 'Values specified in file must convert to an array of length 2'
1124        assert len(q) == 2, msg
1125
1126        self.filename = filename
1127        self.domain = domain
1128        self.starttime = starttime
1129
1130
1131        if domain.starttime is None:
1132            domain.starttime = starttime
1133        else:
1134            msg = 'Start time as specified in domain (%s) is earlier '
1135            'than the starttime of file %s: %s'\
1136                  %(domain.starttime, self.filename, self.starttime) 
1137            if self.starttime > domain.starttime:
1138                raise msg
1139
1140
1141        self.read_times() #Now read all times in.
1142
1143       
1144    def read_times(self):
1145        from Numeric import zeros, Float, alltrue
1146        from config import time_format
1147        import time
1148       
1149        fid = open(self.filename)       
1150        lines = fid.readlines()
1151        fid.close()
1152       
1153        N = len(lines)
1154
1155        T = zeros(N, Float)       #Time
1156        Q = zeros((N, 2), Float)  #Wind field (s, phi)
1157       
1158        for i, line in enumerate(lines):
1159            fields = line.split(',')
1160            real_time = time.mktime(time.strptime(fields[0], time_format))
1161
1162            T[i] = real_time - self.start_time
1163
1164            for j, value in enumerate(fields[1].split()):
1165                Q[i, j] = float(value)
1166
1167        msg = 'File %s must list time as a monotonuosly ' %self.filename
1168        msg += 'increasing sequence'
1169        assert alltrue( T[1:] - T[:-1] > 0 ), msg
1170
1171        self.T = T     #Time
1172        self.Q = Q     #Windfied
1173        self.index = 0 #Initial index
1174
1175       
1176    def __repr__(self):
1177        return 'Wind field from file'
1178
1179       
1180
1181    def __call__(self, domain):
1182        """Evaluate windfield based on values found in domain
1183        """
1184
1185        from math import pi, cos, sin, sqrt
1186       
1187        xmom_update = domain.quantities['xmomentum'].explicit_update
1188        ymom_update = domain.quantities['ymomentum'].explicit_update   
1189       
1190        N = domain.number_of_elements
1191        t = self.domain.time
1192       
1193        msg = 'Time given in File %s does not match model time'\
1194              %self.filename
1195       
1196        if t < self.T[0]: raise msg
1197        if t > self.T[-1]: raise msg       
1198
1199        oldindex = self.index
1200
1201        #Find slot
1202        while t > self.T[self.index]: self.index += 1
1203        while t < self.T[self.index]: self.index -= 1
1204
1205        #t is now between index and index+1
1206        ratio = (t - self.T[self.index])/\
1207                (self.T[self.index+1] - self.T[self.index])
1208
1209        #Compute interpolated values for s and phi
1210        q = self.Q[self.index,:] +\
1211            ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
1212
1213        s = q[0]
1214       
1215        #Convert to radians
1216        phi = q[1]*pi/180
1217       
1218        #Compute velocity vector (u, v)
1219        u = s*cos(phi)
1220        v = s*sin(phi)
1221
1222        #Compute wind stress for this time step
1223        S = self.const * sqrt(u**2 + v**2)
1224        xmom_update += S*u
1225        ymom_update += S*v       
1226       
1227
1228
1229
1230
1231###########################
1232###########################
1233#Geometries
1234
1235
1236#FIXME: Rethink this way of creating values.
1237
1238
1239class Weir:
1240    """Set a bathymetry for weir with a hole and a downstream gutter
1241    x,y are assumed to be in the unit square
1242    """
1243   
1244    def __init__(self, stage):
1245        self.inflow_stage = stage
1246
1247    def __call__(self, x, y):   
1248        from Numeric import zeros, Float
1249        from math import sqrt
1250   
1251        N = len(x)
1252        assert N == len(y)   
1253
1254        z = zeros(N, Float)
1255        for i in range(N):
1256            z[i] = -x[i]/2  #General slope
1257
1258            #Flattish bit to the left
1259            if x[i] < 0.3:
1260                z[i] = -x[i]/10 
1261           
1262            #Weir
1263            if x[i] >= 0.3 and x[i] < 0.4:
1264                z[i] = -x[i]+0.9
1265
1266            #Dip
1267            x0 = 0.6
1268            #depth = -1.3
1269            depth = -1.0
1270            #plateaux = -0.9
1271            plateaux = -0.6           
1272            if y[i] < 0.7:
1273                if x[i] > x0 and x[i] < 0.9:
1274                    z[i] = depth
1275
1276                #RHS plateaux
1277                if x[i] >= 0.9:
1278                    z[i] = plateaux
1279                   
1280                   
1281            elif y[i] >= 0.7 and y[i] < 1.5:
1282                #Restrict and deepen
1283                if x[i] >= x0 and x[i] < 0.8:
1284                    z[i] = depth-(y[i]/3-0.3)
1285                    #z[i] = depth-y[i]/5
1286                    #z[i] = depth
1287                elif x[i] >= 0.8:   
1288                    #RHS plateaux
1289                    z[i] = plateaux
1290                   
1291            elif y[i] >= 1.5:
1292                if x[i] >= x0 and x[i] < 0.8 + (y[i]-1.5)/1.2:
1293                    #Widen up and stay at constant depth
1294                    z[i] = depth-1.5/5
1295                elif x[i] >= 0.8 + (y[i]-1.5)/1.2:                       
1296                    #RHS plateaux
1297                    z[i] = plateaux                                   
1298                   
1299
1300            #Hole in weir (slightly higher than inflow condition)
1301            if x[i] >= 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1302                z[i] = -x[i]+self.inflow_stage + 0.02
1303
1304            #Channel behind weir
1305            x0 = 0.5
1306            if x[i] >= 0.4 and x[i] < x0 and y[i] > 0.2 and y[i] < 0.4:
1307                z[i] = -x[i]+self.inflow_stage + 0.02
1308               
1309            if x[i] >= x0 and x[i] < 0.6 and y[i] > 0.2 and y[i] < 0.4:
1310                #Flatten it out towards the end
1311                z[i] = -x0+self.inflow_stage + 0.02 + (x0-x[i])/5
1312
1313            #Hole to the east
1314            x0 = 1.1; y0 = 0.35
1315            #if x[i] < -0.2 and y < 0.5:
1316            if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1317                z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-1.0
1318
1319            #Tiny channel draining hole
1320            if x[i] >= 1.14 and x[i] < 1.2 and y[i] >= 0.4 and y[i] < 0.6:
1321                z[i] = -0.9 #North south
1322               
1323            if x[i] >= 0.9 and x[i] < 1.18 and y[i] >= 0.58 and y[i] < 0.65:
1324                z[i] = -1.0 + (x[i]-0.9)/3 #East west
1325           
1326           
1327
1328            #Stuff not in use
1329           
1330            #Upward slope at inlet to the north west
1331            #if x[i] < 0.0: # and y[i] > 0.5:
1332            #    #z[i] = -y[i]+0.5  #-x[i]/2
1333            #    z[i] = x[i]/4 - y[i]**2 + 0.5
1334
1335            #Hole to the west
1336            #x0 = -0.4; y0 = 0.35 # center
1337            #if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1338            #    z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-0.2
1339
1340
1341
1342
1343
1344        return z/2
1345
1346class Weir_simple:
1347    """Set a bathymetry for weir with a hole and a downstream gutter
1348    x,y are assumed to be in the unit square
1349    """
1350   
1351    def __init__(self, stage):
1352        self.inflow_stage = stage
1353
1354    def __call__(self, x, y):   
1355        from Numeric import zeros, Float
1356   
1357        N = len(x)
1358        assert N == len(y)   
1359
1360        z = zeros(N, Float)
1361        for i in range(N):
1362            z[i] = -x[i]  #General slope
1363
1364            #Flat bit to the left
1365            if x[i] < 0.3:
1366                z[i] = -x[i]/10  #General slope
1367           
1368            #Weir
1369            if x[i] > 0.3 and x[i] < 0.4:
1370                z[i] = -x[i]+0.9
1371
1372            #Dip
1373            if x[i] > 0.6 and x[i] < 0.9:
1374                z[i] = -x[i]-0.5  #-y[i]/5
1375
1376            #Hole in weir (slightly higher than inflow condition)
1377            if x[i] > 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1378                z[i] = -x[i]+self.inflow_stage + 0.05       
1379           
1380
1381        return z/2
1382       
1383
1384           
1385class Constant_height:
1386    """Set an initial condition with constant water height, e.g
1387    stage s = z+h
1388    """
1389    def __init__(self, W, h):
1390        self.W = W
1391        self.h = h
1392
1393    def __call__(self, x, y):
1394        if self.W is None:
1395            from Numeric import ones, Float
1396            return self.h*ones(len(x), Float)
1397        else:
1398            return self.W(x,y) + self.h
1399
1400
1401
1402##############################################
1403#Initialise module
1404
1405
1406import compile
1407if compile.can_use_C_extension('shallow_water_ext.c'):
1408    #Replace python version with c implementations
1409   
1410    from shallow_water_ext import rotate, assign_windfield_values
1411    compute_fluxes = compute_fluxes_c
1412    gravity = gravity_c
1413    manning_friction = manning_friction_c
1414    balance_deep_and_shallow = balance_deep_and_shallow_c
1415    protect_against_infinitesimal_and_negative_heights = protect_against_infinitesimal_and_negative_heights_c
1416
1417   
1418    #distribute_to_vertices_and_edges = distribute_to_vertices_and_edges_c
1419
1420
1421#Optimisation with psyco
1422from config import use_psyco
1423if use_psyco:
1424    try:
1425        import psyco
1426    except:
1427        msg = 'WARNING: psyco (speedup) could not import'+\
1428              ', you may want to consider installing it'
1429        print msg
1430    else:
1431        psyco.bind(Domain.distribute_to_vertices_and_edges)
1432        psyco.bind(Domain.compute_fluxes)
1433       
1434if __name__ == "__main__":
1435    pass
Note: See TracBrowser for help on using the repository browser.