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

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