source: development/pyvolution-1d/shallow_water_1d.py @ 3510

Last change on this file since 3510 was 3510, checked in by jakeman, 18 years ago

Files now contain inmplementation of method in which advection terms and
pressure terms are split. problems are present.

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