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

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

Created and tested general File_function (reading and interpolating time series from file) and used it to refactor and simplify File_boundary

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