source: inundation/pyvolution/shallow_water.py @ 2950

Last change on this file since 2950 was 2814, checked in by steve, 19 years ago

Resolving conflicts between Ole's new cache argument and the ghost and full dictionary for domain

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 57.5 KB
RevLine 
[2598]1"""Class Domain -
22D triangular domains for finite-volume computations of
3the shallow water wave equation.
4
5This module contains a specialisation of class Domain from module domain.py
6consisting of methods specific to the Shallow Water Wave Equation
7
8
9U_t + E_x + G_y = S
10
11where
12
13U = [w, uh, vh]
14E = [uh, u^2h + gh^2/2, uvh]
15G = [vh, uvh, v^2h + gh^2/2]
16S represents source terms forcing the system
17(e.g. gravity, friction, wind stress, ...)
18
19and _t, _x, _y denote the derivative with respect to t, x and y respectively.
20
21The quantities are
22
23symbol    variable name    explanation
24x         x                horizontal distance from origin [m]
25y         y                vertical distance from origin [m]
26z         elevation        elevation of bed on which flow is modelled [m]
27h         height           water height above z [m]
28w         stage            absolute water level, w = z+h [m]
29u                          speed in the x direction [m/s]
30v                          speed in the y direction [m/s]
31uh        xmomentum        momentum in the x direction [m^2/s]
32vh        ymomentum        momentum in the y direction [m^2/s]
33
34eta                        mannings friction coefficient [to appear]
35nu                         wind stress coefficient [to appear]
36
37The conserved quantities are w, uh, vh
38
39
40For details see e.g.
41Christopher Zoppou and Stephen Roberts,
42Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
43Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
44
45
46Ole Nielsen, Stephen Roberts, Duncan Gray, Christopher Zoppou
47Geoscience Australia, 2004
48"""
49
50#Subversion keywords:
51#
52#$LastChangedDate: 2006-05-05 11:26:06 +0000 (Fri, 05 May 2006) $
53#$LastChangedRevision: 2814 $
54#$LastChangedBy: sexton $
55
56
57from domain import Domain as Generic_Domain
58from generic_boundary_conditions import Boundary
59from generic_boundary_conditions import File_boundary
60from generic_boundary_conditions import Dirichlet_boundary
61from generic_boundary_conditions import Time_boundary
62from generic_boundary_conditions import Transmissive_boundary
63
64from utilities.numerical_tools import gradient, mean
65
66
67
68#Shallow water domain
69class Domain(Generic_Domain):
70
[2814]71    def __init__(self,
72                 coordinates=None,
73                 vertices=None,
74                 boundary=None,
75                 tagged_elements=None,
76                 geo_reference=None,
[2601]77                 use_inscribed_circle=False,
[2810]78                 mesh_filename=None,
79                 use_cache=False,
[2814]80                 verbose=False,
81                 full_send_dict=None,
82                 ghost_recv_dict=None,
83                 processor=0,
84                 numproc=1):
[2598]85
[2814]86
[2598]87        conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
88        other_quantities = ['elevation', 'friction']
[2814]89        Generic_Domain.__init__(self,
90                                coordinates,
91                                vertices,
92                                boundary,
93                                conserved_quantities,
94                                other_quantities,
95                                tagged_elements,
96                                geo_reference,
97                                use_inscribed_circle,
98                                mesh_filename,
99                                use_cache,
100                                verbose,
101                                full_send_dict,
102                                ghost_recv_dict,
103                                processor,
104                                numproc)
[2598]105
[2814]106
[2598]107        from config import minimum_allowed_height, maximum_allowed_speed, g
108        self.minimum_allowed_height = minimum_allowed_height
[2648]109        self.maximum_allowed_speed = maximum_allowed_speed
[2598]110        self.g = g
111
[2648]112
113        self.forcing_terms.append(manning_friction)
[2598]114        self.forcing_terms.append(gravity)
115
116        #Realtime visualisation
117        self.visualiser = None
118        self.visualise  = False
119        self.visualise_color_stage = False
120        self.visualise_stage_range = 1.0
121        self.visualise_timer = True
122        self.visualise_range_z = None
123
124        #Stored output
125        self.store = True
126        self.format = 'sww'
[2808]127        self.set_store_vertices_uniquely(False)
[2598]128
129        self.quantities_to_be_stored = ['stage','xmomentum','ymomentum']
130
[2814]131
[2808]132    def set_store_vertices_uniquely(self, flag, reduction=None):
133        """Decide whether vertex values should be stored uniquely as
134        computed in the model or whether they should be reduced to one
[2814]135        value per vertex using self.reduction.
[2808]136        """
137        self.smooth = not flag
[2648]138
[2808]139        #Reduction operation for get_vertex_values
140        if reduction is None:
141            self.reduction = mean
142            #self.reduction = min  #Looks better near steep slopes
143
[2814]144
145
146
[2598]147    def set_quantities_to_be_stored(self, q):
148        """Specify which quantities will be stored in the sww file.
149
150        q must be either:
151          - the name of a quantity
152          - a list of quantity names
153          - None
154
155        In the two first cases, the named quantities will be stored at each yieldstep
[2648]156        (This is in addition to the quantities elevation and friction)
[2598]157        If q is None, storage will be switched off altogether.
158        """
159
160
161        if q is None:
[2648]162            self.quantities_to_be_stored = []
[2598]163            self.store = False
164            return
165
166        if isinstance(q, basestring):
167            q = [q] # Turn argument into a list
168
[2648]169        #Check correcness
[2598]170        for quantity_name in q:
171            msg = 'Quantity %s is not a valid conserved quantity' %quantity_name
[2648]172            assert quantity_name in self.conserved_quantities, msg
173
[2598]174        self.quantities_to_be_stored = q
175
[2648]176
[2598]177    def initialise_visualiser(self,scale_z=1.0,rect=None):
178        #Realtime visualisation
179        if self.visualiser is None:
180            from realtime_visualisation_new import Visualiser
181            self.visualiser = Visualiser(self,scale_z,rect)
182            self.visualiser.setup['elevation']=True
183            self.visualiser.updating['stage']=True
184        self.visualise = True
185        if self.visualise_color_stage == True:
186            self.visualiser.coloring['stage'] = True
187            self.visualiser.qcolor['stage'] = (0.0, 0.0, 0.8)
188        print 'initialise visualiser'
189        print self.visualiser.setup
190        print self.visualiser.updating
191
192    def check_integrity(self):
193        Generic_Domain.check_integrity(self)
194
195        #Check that we are solving the shallow water wave equation
196
197        msg = 'First conserved quantity must be "stage"'
198        assert self.conserved_quantities[0] == 'stage', msg
199        msg = 'Second conserved quantity must be "xmomentum"'
200        assert self.conserved_quantities[1] == 'xmomentum', msg
201        msg = 'Third conserved quantity must be "ymomentum"'
202        assert self.conserved_quantities[2] == 'ymomentum', msg
203
204    def extrapolate_second_order_sw(self):
205        #Call correct module function
206        #(either from this module or C-extension)
207        extrapolate_second_order_sw(self)
208
209    def compute_fluxes(self):
210        #Call correct module function
211        #(either from this module or C-extension)
212        compute_fluxes(self)
213
214    def distribute_to_vertices_and_edges(self):
215        #Call correct module function
216        #(either from this module or C-extension)
217        distribute_to_vertices_and_edges(self)
218
219
220    #FIXME: Under construction
221#     def set_defaults(self):
222#         """Set default values for uninitialised quantities.
223#         This is specific to the shallow water wave equation
224#         Defaults for 'elevation', 'friction', 'xmomentum' and 'ymomentum'
225#         are 0.0. Default for 'stage' is whatever the value of 'elevation'.
226#         """
227
228#         for name in self.other_quantities + self.conserved_quantities:
229#             print name
230#             print self.quantities.keys()
231#             if not self.quantities.has_key(name):
232#                 if name == 'stage':
233
234#                     if self.quantities.has_key('elevation'):
235#                         z = self.quantities['elevation'].vertex_values
236#                         self.set_quantity(name, z)
237#                     else:
238#                         self.set_quantity(name, 0.0)
239#                 else:
240#                     self.set_quantity(name, 0.0)
241
242
243
244#         #Lift negative heights up
245#         #z = self.quantities['elevation'].vertex_values
246#         #w = self.quantities['stage'].vertex_values
247
248#         #h = w-z
249
250#         #for k in range(h.shape[0]):
251#         #    for i in range(3):
252#         #        if h[k, i] < 0.0:
253#         #            w[k, i] = z[k, i]
254
255
256#         #self.quantities['stage'].interpolate()
257
258
259    def evolve(self,
260               yieldstep = None,
261               finaltime = None,
[2648]262               duration = None,
[2598]263               skip_initial_step = False):
264        """Specialisation of basic evolve method from parent class
265        """
266
267        #Call check integrity here rather than from user scripts
268        #self.check_integrity()
269
270        msg = 'Parameter beta_h must be in the interval [0, 1['
271        assert 0 <= self.beta_h < 1.0, msg
272        msg = 'Parameter beta_w must be in the interval [0, 1['
273        assert 0 <= self.beta_w < 1.0, msg
274
275
276        #Initial update of vertex and edge values before any storage
277        #and or visualisation
278        self.distribute_to_vertices_and_edges()
279
280        #Initialise real time viz if requested
281        if self.visualise is True and self.time == 0.0:
282            if self.visualiser is None:
283                self.initialise_visualiser()
284
285            self.visualiser.update_timer()
286            self.visualiser.setup_all()
287
288        #Store model data, e.g. for visualisation
289        if self.store is True and self.time == 0.0:
290            self.initialise_storage()
291            #print 'Storing results in ' + self.writer.filename
292        else:
293            pass
294            #print 'Results will not be stored.'
295            #print 'To store results set domain.store = True'
296            #FIXME: Diagnostic output should be controlled by
297            # a 'verbose' flag living in domain (or in a parent class)
298
299        #Call basic machinery from parent class
300        for t in Generic_Domain.evolve(self,
301                                       yieldstep=yieldstep,
302                                       finaltime=finaltime,
303                                       duration=duration,
304                                       skip_initial_step=skip_initial_step):
305            #Real time viz
306            if self.visualise is True:
307                self.visualiser.update_all()
308                self.visualiser.update_timer()
309
310
311            #Store model data, e.g. for subsequent visualisation
312            if self.store is True:
313                self.store_timestep(self.quantities_to_be_stored)
314
315            #FIXME: Could maybe be taken from specified list
316            #of 'store every step' quantities
317
318            #Pass control on to outer loop for more specific actions
319            yield(t)
320
321    def initialise_storage(self):
322        """Create and initialise self.writer object for storing data.
323        Also, save x,y and bed elevation
324        """
325
326        import data_manager
327
328        #Initialise writer
329        self.writer = data_manager.get_dataobject(self, mode = 'w')
330
331        #Store vertices and connectivity
332        self.writer.store_connectivity()
333
334
335    def store_timestep(self, name):
336        """Store named quantity and time.
337
338        Precondition:
339           self.write has been initialised
340        """
341        self.writer.store_timestep(name)
342
343
344#=============== End of Shallow Water Domain ===============================
345
346
347
348#Rotation of momentum vector
349def rotate(q, normal, direction = 1):
350    """Rotate the momentum component q (q[1], q[2])
351    from x,y coordinates to coordinates based on normal vector.
352
353    If direction is negative the rotation is inverted.
354
355    Input vector is preserved
356
357    This function is specific to the shallow water wave equation
358    """
359
360    from Numeric import zeros, Float
361
362    assert len(q) == 3,\
363           'Vector of conserved quantities must have length 3'\
364           'for 2D shallow water equation'
365
366    try:
367        l = len(normal)
368    except:
369        raise 'Normal vector must be an Numeric array'
370
371    assert l == 2, 'Normal vector must have 2 components'
372
373
374    n1 = normal[0]
375    n2 = normal[1]
376
377    r = zeros(len(q), Float) #Rotated quantities
378    r[0] = q[0]              #First quantity, height, is not rotated
379
380    if direction == -1:
381        n2 = -n2
382
383
384    r[1] =  n1*q[1] + n2*q[2]
385    r[2] = -n2*q[1] + n1*q[2]
386
387    return r
388
389
390
391####################################
392# Flux computation
393def flux_function(normal, ql, qr, zl, zr):
394    """Compute fluxes between volumes for the shallow water wave equation
395    cast in terms of w = h+z using the 'central scheme' as described in
396
397    Kurganov, Noelle, Petrova. 'Semidiscrete Central-Upwind Schemes For
398    Hyperbolic Conservation Laws and Hamilton-Jacobi Equations'.
399    Siam J. Sci. Comput. Vol. 23, No. 3, pp. 707-740.
400
401    The implemented formula is given in equation (3.15) on page 714
402
403    Conserved quantities w, uh, vh are stored as elements 0, 1 and 2
404    in the numerical vectors ql and qr.
405
406    Bed elevations zl and zr.
407    """
408
409    from config import g, epsilon
410    from math import sqrt
411    from Numeric import array
412
413    #Align momentums with x-axis
414    q_left  = rotate(ql, normal, direction = 1)
415    q_right = rotate(qr, normal, direction = 1)
416
417    z = (zl+zr)/2 #Take average of field values
418
419    w_left  = q_left[0]   #w=h+z
420    h_left  = w_left-z
421    uh_left = q_left[1]
422
423    if h_left < epsilon:
424        u_left = 0.0  #Could have been negative
425        h_left = 0.0
426    else:
427        u_left  = uh_left/h_left
428
429
430    w_right  = q_right[0]  #w=h+z
431    h_right  = w_right-z
432    uh_right = q_right[1]
433
434
435    if h_right < epsilon:
436        u_right = 0.0  #Could have been negative
437        h_right = 0.0
438    else:
439        u_right  = uh_right/h_right
440
441    vh_left  = q_left[2]
442    vh_right = q_right[2]
443
444    soundspeed_left  = sqrt(g*h_left)
445    soundspeed_right = sqrt(g*h_right)
446
447    #Maximal wave speed
448    s_max = max(u_left+soundspeed_left, u_right+soundspeed_right, 0)
449
450    #Minimal wave speed
451    s_min = min(u_left-soundspeed_left, u_right-soundspeed_right, 0)
452
453    #Flux computation
454
455    #FIXME(Ole): Why is it again that we don't
456    #use uh_left and uh_right directly in the first entries?
457    flux_left  = array([u_left*h_left,
458                        u_left*uh_left + 0.5*g*h_left**2,
459                        u_left*vh_left])
460    flux_right = array([u_right*h_right,
461                        u_right*uh_right + 0.5*g*h_right**2,
462                        u_right*vh_right])
463
464    denom = s_max-s_min
465    if denom == 0.0:
466        edgeflux = array([0.0, 0.0, 0.0])
467        max_speed = 0.0
468    else:
469        edgeflux = (s_max*flux_left - s_min*flux_right)/denom
470        edgeflux += s_max*s_min*(q_right-q_left)/denom
471
472        edgeflux = rotate(edgeflux, normal, direction=-1)
473        max_speed = max(abs(s_max), abs(s_min))
474
475    return edgeflux, max_speed
476
477
478def compute_fluxes(domain):
479    """Compute all fluxes and the timestep suitable for all volumes
480    in domain.
481
482    Compute total flux for each conserved quantity using "flux_function"
483
484    Fluxes across each edge are scaled by edgelengths and summed up
485    Resulting flux is then scaled by area and stored in
486    explicit_update for each of the three conserved quantities
487    stage, xmomentum and ymomentum
488
489    The maximal allowable speed computed by the flux_function for each volume
490    is converted to a timestep that must not be exceeded. The minimum of
491    those is computed as the next overall timestep.
492
493    Post conditions:
494      domain.explicit_update is reset to computed flux values
495      domain.timestep is set to the largest step satisfying all volumes.
496    """
497
498    import sys
499    from Numeric import zeros, Float
500
501    N = domain.number_of_elements
502
503    #Shortcuts
504    Stage = domain.quantities['stage']
505    Xmom = domain.quantities['xmomentum']
506    Ymom = domain.quantities['ymomentum']
507    Bed = domain.quantities['elevation']
508
509    #Arrays
510    stage = Stage.edge_values
511    xmom =  Xmom.edge_values
512    ymom =  Ymom.edge_values
513    bed =   Bed.edge_values
514
515    stage_bdry = Stage.boundary_values
516    xmom_bdry =  Xmom.boundary_values
517    ymom_bdry =  Ymom.boundary_values
518
519    flux = zeros(3, Float) #Work array for summing up fluxes
520
521
522    #Loop
523    timestep = float(sys.maxint)
524    for k in range(N):
525
526        flux[:] = 0.  #Reset work array
527        for i in range(3):
528            #Quantities inside volume facing neighbour i
529            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
530            zl = bed[k, i]
531
532            #Quantities at neighbour on nearest face
533            n = domain.neighbours[k,i]
534            if n < 0:
535                m = -n-1 #Convert negative flag to index
536                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
537                zr = zl #Extend bed elevation to boundary
538            else:
539                m = domain.neighbour_edges[k,i]
540                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
541                zr = bed[n, m]
542
543
544            #Outward pointing normal vector
545            normal = domain.normals[k, 2*i:2*i+2]
546
547            #Flux computation using provided function
548            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
549            flux -= edgeflux * domain.edgelengths[k,i]
550
551            #Update optimal_timestep
552            try:
553                timestep = min(timestep, 0.5*domain.radii[k]/max_speed)
554            except ZeroDivisionError:
555                pass
556
557        #Normalise by area and store for when all conserved
558        #quantities get updated
559        flux /= domain.areas[k]
560        Stage.explicit_update[k] = flux[0]
561        Xmom.explicit_update[k] = flux[1]
562        Ymom.explicit_update[k] = flux[2]
563
564
565    domain.timestep = timestep
566
567#MH090605 The following method belongs to the shallow_water domain class
568#see comments in the corresponding method in shallow_water_ext.c
569def extrapolate_second_order_sw_c(domain):
570    """Wrapper calling C version of extrapolate_second_order_sw
571    """
572    import sys
573    from Numeric import zeros, Float
574
575    N = domain.number_of_elements
576
577    #Shortcuts
578    Stage = domain.quantities['stage']
579    Xmom = domain.quantities['xmomentum']
580    Ymom = domain.quantities['ymomentum']
581    from shallow_water_ext import extrapolate_second_order_sw
582    extrapolate_second_order_sw(domain,domain.surrogate_neighbours,
583                                domain.number_of_boundaries,
584                                domain.centroid_coordinates,
585                                Stage.centroid_values,
586                                Xmom.centroid_values,
587                                Ymom.centroid_values,
588                                domain.vertex_coordinates,
589                                Stage.vertex_values,
590                                Xmom.vertex_values,
591                                Ymom.vertex_values)
592
593def compute_fluxes_c(domain):
594    """Wrapper calling C version of compute fluxes
595    """
596
597    import sys
598    from Numeric import zeros, Float
599
600    N = domain.number_of_elements
601
602    #Shortcuts
603    Stage = domain.quantities['stage']
604    Xmom = domain.quantities['xmomentum']
605    Ymom = domain.quantities['ymomentum']
606    Bed = domain.quantities['elevation']
607
608    timestep = float(sys.maxint)
609    from shallow_water_ext import compute_fluxes
610    domain.timestep = compute_fluxes(timestep, domain.epsilon, domain.g,
611                                     domain.neighbours,
612                                     domain.neighbour_edges,
613                                     domain.normals,
614                                     domain.edgelengths,
615                                     domain.radii,
616                                     domain.areas,
617                                     Stage.edge_values,
618                                     Xmom.edge_values,
619                                     Ymom.edge_values,
620                                     Bed.edge_values,
621                                     Stage.boundary_values,
622                                     Xmom.boundary_values,
623                                     Ymom.boundary_values,
624                                     Stage.explicit_update,
625                                     Xmom.explicit_update,
626                                     Ymom.explicit_update,
627                                     domain.already_computed_flux)
628
629
630####################################
631# Module functions for gradient limiting (distribute_to_vertices_and_edges)
632
633def distribute_to_vertices_and_edges(domain):
634    """Distribution from centroids to vertices specific to the
635    shallow water wave
636    equation.
637
638    It will ensure that h (w-z) is always non-negative even in the
639    presence of steep bed-slopes by taking a weighted average between shallow
640    and deep cases.
641
642    In addition, all conserved quantities get distributed as per either a
643    constant (order==1) or a piecewise linear function (order==2).
644
645    FIXME: more explanation about removal of artificial variability etc
646
647    Precondition:
648      All quantities defined at centroids and bed elevation defined at
649      vertices.
650
651    Postcondition
652      Conserved quantities defined at vertices
653
654    """
655
656    from config import optimised_gradient_limiter
657
658    #Remove very thin layers of water
659    protect_against_infinitesimal_and_negative_heights(domain)
660
661    #Extrapolate all conserved quantities
662    if optimised_gradient_limiter:
663        #MH090605 if second order,
664        #perform the extrapolation and limiting on
665        #all of the conserved quantitie
666
667        if (domain.order == 1):
668            for name in domain.conserved_quantities:
669                Q = domain.quantities[name]
670                Q.extrapolate_first_order()
671        elif domain.order == 2:
672            domain.extrapolate_second_order_sw()
673        else:
674            raise 'Unknown order'
675    else:
676        #old code:
677        for name in domain.conserved_quantities:
678            Q = domain.quantities[name]
679            if domain.order == 1:
680                Q.extrapolate_first_order()
681            elif domain.order == 2:
682                Q.extrapolate_second_order()
683                Q.limit()
684            else:
685                raise 'Unknown order'
686
687
688    #Take bed elevation into account when water heights are small
689    balance_deep_and_shallow(domain)
690
691    #Compute edge values by interpolation
692    for name in domain.conserved_quantities:
693        Q = domain.quantities[name]
694        Q.interpolate_from_vertices_to_edges()
695
696
697def protect_against_infinitesimal_and_negative_heights(domain):
698    """Protect against infinitesimal heights and associated high velocities
699    """
700
701    #Shortcuts
702    wc = domain.quantities['stage'].centroid_values
703    zc = domain.quantities['elevation'].centroid_values
704    xmomc = domain.quantities['xmomentum'].centroid_values
705    ymomc = domain.quantities['ymomentum'].centroid_values
706    hc = wc - zc  #Water depths at centroids
707
[2648]708    #Update
[2598]709    #FIXME: Modify accroditg to c-version - or discard altogether.
710    for k in range(domain.number_of_elements):
711
712        if hc[k] < domain.minimum_allowed_height:
713            #Control stage
714            if hc[k] < domain.epsilon:
715                wc[k] = zc[k] # Contain 'lost mass' error
716
717            #Control momentum
718            xmomc[k] = ymomc[k] = 0.0
719
720
721def protect_against_infinitesimal_and_negative_heights_c(domain):
722    """Protect against infinitesimal heights and associated high velocities
723    """
724
725    #Shortcuts
726    wc = domain.quantities['stage'].centroid_values
727    zc = domain.quantities['elevation'].centroid_values
728    xmomc = domain.quantities['xmomentum'].centroid_values
729    ymomc = domain.quantities['ymomentum'].centroid_values
730
731    from shallow_water_ext import protect
732
[2648]733    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
[2598]734            domain.epsilon, wc, zc, xmomc, ymomc)
735
736
737
738def h_limiter(domain):
739    """Limit slopes for each volume to eliminate artificial variance
740    introduced by e.g. second order extrapolator
741
742    limit on h = w-z
743
744    This limiter depends on two quantities (w,z) so it resides within
745    this module rather than within quantity.py
746    """
747
748    from Numeric import zeros, Float
749
750    N = domain.number_of_elements
751    beta_h = domain.beta_h
752
753    #Shortcuts
754    wc = domain.quantities['stage'].centroid_values
755    zc = domain.quantities['elevation'].centroid_values
756    hc = wc - zc
757
758    wv = domain.quantities['stage'].vertex_values
759    zv = domain.quantities['elevation'].vertex_values
760    hv = wv-zv
761
762    hvbar = zeros(hv.shape, Float) #h-limited values
763
764    #Find min and max of this and neighbour's centroid values
765    hmax = zeros(hc.shape, Float)
766    hmin = zeros(hc.shape, Float)
767
768    for k in range(N):
769        hmax[k] = hmin[k] = hc[k]
770        for i in range(3):
771            n = domain.neighbours[k,i]
772            if n >= 0:
773                hn = hc[n] #Neighbour's centroid value
774
775                hmin[k] = min(hmin[k], hn)
776                hmax[k] = max(hmax[k], hn)
777
778
779    #Diffences between centroids and maxima/minima
780    dhmax = hmax - hc
781    dhmin = hmin - hc
782
783    #Deltas between vertex and centroid values
784    dh = zeros(hv.shape, Float)
785    for i in range(3):
786        dh[:,i] = hv[:,i] - hc
787
788    #Phi limiter
789    for k in range(N):
790
791        #Find the gradient limiter (phi) across vertices
792        phi = 1.0
793        for i in range(3):
794            r = 1.0
795            if (dh[k,i] > 0): r = dhmax[k]/dh[k,i]
796            if (dh[k,i] < 0): r = dhmin[k]/dh[k,i]
797
798            phi = min( min(r*beta_h, 1), phi )
799
800        #Then update using phi limiter
801        for i in range(3):
802            hvbar[k,i] = hc[k] + phi*dh[k,i]
803
804    return hvbar
805
806
807
808def h_limiter_c(domain):
809    """Limit slopes for each volume to eliminate artificial variance
810    introduced by e.g. second order extrapolator
811
812    limit on h = w-z
813
814    This limiter depends on two quantities (w,z) so it resides within
815    this module rather than within quantity.py
816
817    Wrapper for c-extension
818    """
819
820    from Numeric import zeros, Float
821
822    N = domain.number_of_elements
823    beta_h = domain.beta_h
824
825    #Shortcuts
826    wc = domain.quantities['stage'].centroid_values
827    zc = domain.quantities['elevation'].centroid_values
828    hc = wc - zc
829
830    wv = domain.quantities['stage'].vertex_values
831    zv = domain.quantities['elevation'].vertex_values
832    hv = wv - zv
833
834    #Call C-extension
835    from shallow_water_ext import h_limiter_sw as h_limiter
836    hvbar = h_limiter(domain, hc, hv)
837
838    return hvbar
839
840
841def balance_deep_and_shallow(domain):
842    """Compute linear combination between stage as computed by
843    gradient-limiters limiting using w, and stage computed by
844    gradient-limiters limiting using h (h-limiter).
845    The former takes precedence when heights are large compared to the
846    bed slope while the latter takes precedence when heights are
847    relatively small.  Anything in between is computed as a balanced
848    linear combination in order to avoid numerical disturbances which
849    would otherwise appear as a result of hard switching between
850    modes.
851
852    The h-limiter is always applied irrespective of the order.
853    """
854
855    #Shortcuts
856    wc = domain.quantities['stage'].centroid_values
857    zc = domain.quantities['elevation'].centroid_values
858    hc = wc - zc
859
860    wv = domain.quantities['stage'].vertex_values
861    zv = domain.quantities['elevation'].vertex_values
862    hv = wv-zv
863
864    #Limit h
865    hvbar = h_limiter(domain)
866
867    for k in range(domain.number_of_elements):
868        #Compute maximal variation in bed elevation
869        #  This quantitiy is
870        #    dz = max_i abs(z_i - z_c)
871        #  and it is independent of dimension
872        #  In the 1d case zc = (z0+z1)/2
873        #  In the 2d case zc = (z0+z1+z2)/3
874
875        dz = max(abs(zv[k,0]-zc[k]),
876                 abs(zv[k,1]-zc[k]),
877                 abs(zv[k,2]-zc[k]))
878
879
880        hmin = min( hv[k,:] )
881
882        #Create alpha in [0,1], where alpha==0 means using the h-limited
883        #stage and alpha==1 means using the w-limited stage as
884        #computed by the gradient limiter (both 1st or 2nd order)
885
886        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
887        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
888
889        if dz > 0.0:
890            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
891        else:
892            #Flat bed
893            alpha = 1.0
894
895        #Let
896        #
897        #  wvi be the w-limited stage (wvi = zvi + hvi)
898        #  wvi- be the h-limited state (wvi- = zvi + hvi-)
899        #
900        #
901        #where i=0,1,2 denotes the vertex ids
902        #
903        #Weighted balance between w-limited and h-limited stage is
904        #
905        #  wvi := (1-alpha)*(zvi+hvi-) + alpha*(zvi+hvi)
906        #
907        #It follows that the updated wvi is
908        #  wvi := zvi + (1-alpha)*hvi- + alpha*hvi
909        #
910        # Momentum is balanced between constant and limited
911
912
913        #for i in range(3):
914        #    wv[k,i] = zv[k,i] + hvbar[k,i]
915
916        #return
917
918        if alpha < 1:
919
920            for i in range(3):
921                wv[k,i] = zv[k,i] + (1-alpha)*hvbar[k,i] + alpha*hv[k,i]
922
923            #Momentums at centroids
924            xmomc = domain.quantities['xmomentum'].centroid_values
925            ymomc = domain.quantities['ymomentum'].centroid_values
926
927            #Momentums at vertices
928            xmomv = domain.quantities['xmomentum'].vertex_values
929            ymomv = domain.quantities['ymomentum'].vertex_values
930
931            # Update momentum as a linear combination of
932            # xmomc and ymomc (shallow) and momentum
933            # from extrapolator xmomv and ymomv (deep).
934            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
935            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
936
937
938def balance_deep_and_shallow_c(domain):
939    """Wrapper for C implementation
940    """
941
942    #Shortcuts
943    wc = domain.quantities['stage'].centroid_values
944    zc = domain.quantities['elevation'].centroid_values
945    hc = wc - zc
946
947    wv = domain.quantities['stage'].vertex_values
948    zv = domain.quantities['elevation'].vertex_values
949    hv = wv - zv
950
951    #Momentums at centroids
952    xmomc = domain.quantities['xmomentum'].centroid_values
953    ymomc = domain.quantities['ymomentum'].centroid_values
954
955    #Momentums at vertices
956    xmomv = domain.quantities['xmomentum'].vertex_values
957    ymomv = domain.quantities['ymomentum'].vertex_values
958
959    #Limit h
960    hvbar = h_limiter(domain)
961
962    #This is how one would make a first order h_limited value
963    #as in the old balancer (pre 17 Feb 2005):
964    #from Numeric import zeros, Float
965    #hvbar = zeros( (len(hc), 3), Float)
966    #for i in range(3):
967    #    hvbar[:,i] = hc[:]
968
969    from shallow_water_ext import balance_deep_and_shallow
970    balance_deep_and_shallow(wc, zc, hc, wv, zv, hv, hvbar,
971                             xmomc, ymomc, xmomv, ymomv)
972
973
974
975
976###############################################
977#Boundaries - specific to the shallow water wave equation
978class Reflective_boundary(Boundary):
979    """Reflective boundary returns same conserved quantities as
980    those present in its neighbour volume but reflected.
981
982    This class is specific to the shallow water equation as it
983    works with the momentum quantities assumed to be the second
984    and third conserved quantities.
985    """
986
987    def __init__(self, domain = None):
988        Boundary.__init__(self)
989
990        if domain is None:
991            msg = 'Domain must be specified for reflective boundary'
992            raise msg
993
994        #Handy shorthands
995        self.stage   = domain.quantities['stage'].edge_values
996        self.xmom    = domain.quantities['xmomentum'].edge_values
997        self.ymom    = domain.quantities['ymomentum'].edge_values
998        self.normals = domain.normals
999
1000        from Numeric import zeros, Float
1001        self.conserved_quantities = zeros(3, Float)
1002
1003    def __repr__(self):
1004        return 'Reflective_boundary'
1005
1006
1007    def evaluate(self, vol_id, edge_id):
1008        """Reflective boundaries reverses the outward momentum
1009        of the volume they serve.
1010        """
1011
1012        q = self.conserved_quantities
1013        q[0] = self.stage[vol_id, edge_id]
1014        q[1] = self.xmom[vol_id, edge_id]
1015        q[2] = self.ymom[vol_id, edge_id]
1016
1017        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
1018
1019
1020        r = rotate(q, normal, direction = 1)
1021        r[1] = -r[1]
1022        q = rotate(r, normal, direction = -1)
1023
1024        return q
1025
1026
1027
1028class Transmissive_Momentum_Set_Stage_boundary(Boundary):
1029    """Returns same momentum conserved quantities as
1030    those present in its neighbour volume. Sets stage
1031
1032    Underlying domain must be specified when boundary is instantiated
1033    """
1034
1035    def __init__(self, domain = None, function=None):
1036        Boundary.__init__(self)
1037
1038        if domain is None:
1039            msg = 'Domain must be specified for this type boundary'
1040            raise msg
1041
1042        if function is None:
1043            msg = 'Function must be specified for this type boundary'
1044            raise msg
1045
1046        self.domain   = domain
1047        self.function = function
1048
1049    def __repr__(self):
1050        return 'Transmissive_Momentum_Set_Stage_boundary(%s)' %self.domain
1051
1052    def evaluate(self, vol_id, edge_id):
1053        """Transmissive Momentum Set Stage boundaries return the edge momentum
1054        values of the volume they serve.
1055        """
1056
1057        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
1058        value = self.function(self.domain.time)
1059        q[0] = value[0]
1060        return q
1061
1062
1063        #FIXME: Consider this (taken from File_boundary) to allow
1064        #spatial variation
1065        #if vol_id is not None and edge_id is not None:
1066        #    i = self.boundary_indices[ vol_id, edge_id ]
1067        #    return self.F(t, point_id = i)
1068        #else:
1069        #    return self.F(t)
1070
1071
1072
1073class Dirichlet_Discharge_boundary(Boundary):
1074    """
1075    Sets stage (stage0)
1076    Sets momentum (wh0) in the inward normal direction.
1077
1078    Underlying domain must be specified when boundary is instantiated
1079    """
1080
1081    def __init__(self, domain = None, stage0=None, wh0=None):
1082        Boundary.__init__(self)
1083
1084        if domain is None:
1085            msg = 'Domain must be specified for this type boundary'
1086            raise msg
1087
1088        if stage0 is None:
1089            raise 'set stage'
1090
1091        if wh0 is None:
1092            wh0 = 0.0
1093
1094        self.domain   = domain
1095        self.stage0  = stage0
1096        self.wh0 = wh0
1097
1098    def __repr__(self):
1099        return 'Dirichlet_Discharge_boundary(%s)' %self.domain
1100
1101    def evaluate(self, vol_id, edge_id):
1102        """Set discharge in the (inward) normal direction
1103        """
1104
1105        normal = self.domain.get_normal(vol_id,edge_id)
1106        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1107        return q
1108
1109
1110        #FIXME: Consider this (taken from File_boundary) to allow
1111        #spatial variation
1112        #if vol_id is not None and edge_id is not None:
1113        #    i = self.boundary_indices[ vol_id, edge_id ]
1114        #    return self.F(t, point_id = i)
1115        #else:
1116        #    return self.F(t)
1117
1118
1119
1120#class Spatio_temporal_boundary(Boundary):
1121#    """The spatio-temporal boundary, reads values for the conserved
1122#    quantities from an sww NetCDF file, and returns interpolated values
1123#    at the midpoints of each associated boundaty segment.
1124#    Time dependency is interpolated linearly as in util.File_function.#
1125#
1126#    Example:
1127#    Bf = Spatio_temporal_boundary('source_file.sww', domain)
1128#
1129#    """
1130Spatio_temporal_boundary = File_boundary
1131
1132
1133
1134
1135#########################
1136#Standard forcing terms:
1137#
1138def gravity(domain):
1139    """Apply gravitational pull in the presence of bed slope
1140    """
1141
1142    from Numeric import zeros, Float, array, sum
1143
1144    xmom = domain.quantities['xmomentum'].explicit_update
1145    ymom = domain.quantities['ymomentum'].explicit_update
1146
1147    Stage = domain.quantities['stage']
1148    Elevation = domain.quantities['elevation']
1149    h = Stage.edge_values - Elevation.edge_values
1150    v = Elevation.vertex_values
1151
1152    x = domain.get_vertex_coordinates()
1153    g = domain.g
1154
1155    for k in range(domain.number_of_elements):
1156        avg_h = sum( h[k,:] )/3
1157
1158        #Compute bed slope
1159        x0, y0, x1, y1, x2, y2 = x[k,:]
1160        z0, z1, z2 = v[k,:]
1161
1162        zx, zy = gradient(x0, y0, x1, y1, x2, y2, z0, z1, z2)
1163
1164        #Update momentum
1165        xmom[k] += -g*zx*avg_h
1166        ymom[k] += -g*zy*avg_h
1167
1168
1169def gravity_c(domain):
1170    """Wrapper calling C version
1171    """
1172
1173    xmom = domain.quantities['xmomentum'].explicit_update
1174    ymom = domain.quantities['ymomentum'].explicit_update
1175
1176    Stage = domain.quantities['stage']
1177    Elevation = domain.quantities['elevation']
1178    h = Stage.edge_values - Elevation.edge_values
1179    v = Elevation.vertex_values
1180
1181    x = domain.get_vertex_coordinates()
1182    g = domain.g
1183
1184
1185    from shallow_water_ext import gravity
1186    gravity(g, h, v, x, xmom, ymom)
1187
1188
1189def manning_friction(domain):
1190    """Apply (Manning) friction to water momentum
[2715]1191    (Python version)
[2598]1192    """
1193
1194    from math import sqrt
1195
1196    w = domain.quantities['stage'].centroid_values
1197    z = domain.quantities['elevation'].centroid_values
1198    h = w-z
1199
1200    uh = domain.quantities['xmomentum'].centroid_values
1201    vh = domain.quantities['ymomentum'].centroid_values
1202    eta = domain.quantities['friction'].centroid_values
1203
1204    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1205    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1206
1207    N = domain.number_of_elements
1208    eps = domain.minimum_allowed_height
1209    g = domain.g
1210
1211    for k in range(N):
1212        if eta[k] >= eps:
1213            if h[k] >= eps:
1214                S = -g * eta[k]**2 * sqrt((uh[k]**2 + vh[k]**2))
1215                S /= h[k]**(7.0/3)
1216
1217                #Update momentum
1218                xmom_update[k] += S*uh[k]
1219                ymom_update[k] += S*vh[k]
1220
1221
[2648]1222def manning_friction_implicit_c(domain):
[2598]1223    """Wrapper for c version
1224    """
1225
1226
[2715]1227    #print 'Implicit friction'
[2731]1228
[2598]1229    xmom = domain.quantities['xmomentum']
1230    ymom = domain.quantities['ymomentum']
1231
1232    w = domain.quantities['stage'].centroid_values
1233    z = domain.quantities['elevation'].centroid_values
1234
1235    uh = xmom.centroid_values
1236    vh = ymom.centroid_values
1237    eta = domain.quantities['friction'].centroid_values
1238
1239    xmom_update = xmom.semi_implicit_update
1240    ymom_update = ymom.semi_implicit_update
1241
1242    N = domain.number_of_elements
1243    eps = domain.minimum_allowed_height
1244    g = domain.g
1245
1246    from shallow_water_ext import manning_friction
1247    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1248
1249
[2648]1250def manning_friction_explicit_c(domain):
1251    """Wrapper for c version
1252    """
1253
[2715]1254    #print 'Explicit friction'
[2731]1255
[2648]1256    xmom = domain.quantities['xmomentum']
1257    ymom = domain.quantities['ymomentum']
1258
1259    w = domain.quantities['stage'].centroid_values
1260    z = domain.quantities['elevation'].centroid_values
1261
1262    uh = xmom.centroid_values
1263    vh = ymom.centroid_values
1264    eta = domain.quantities['friction'].centroid_values
1265
1266    xmom_update = xmom.explicit_update
1267    ymom_update = ymom.explicit_update
1268
1269    N = domain.number_of_elements
1270    eps = domain.minimum_allowed_height
1271    g = domain.g
1272
1273    from shallow_water_ext import manning_friction
1274    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1275
1276
[2598]1277def linear_friction(domain):
1278    """Apply linear friction to water momentum
1279
1280    Assumes quantity: 'linear_friction' to be present
1281    """
1282
1283    from math import sqrt
1284
1285    w = domain.quantities['stage'].centroid_values
1286    z = domain.quantities['elevation'].centroid_values
1287    h = w-z
1288
1289    uh = domain.quantities['xmomentum'].centroid_values
1290    vh = domain.quantities['ymomentum'].centroid_values
1291    tau = domain.quantities['linear_friction'].centroid_values
1292
1293    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1294    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1295
1296    N = domain.number_of_elements
1297    eps = domain.minimum_allowed_height
1298    g = domain.g #Not necessary? Why was this added?
1299
1300    for k in range(N):
1301        if tau[k] >= eps:
1302            if h[k] >= eps:
1303                S = -tau[k]/h[k]
1304
1305                #Update momentum
1306                xmom_update[k] += S*uh[k]
1307                ymom_update[k] += S*vh[k]
1308
1309
1310
1311def check_forcefield(f):
1312    """Check that f is either
1313    1: a callable object f(t,x,y), where x and y are vectors
1314       and that it returns an array or a list of same length
1315       as x and y
1316    2: a scalar
1317    """
1318
1319    from Numeric import ones, Float, array
1320
1321
1322    if callable(f):
1323        N = 3
1324        x = ones(3, Float)
1325        y = ones(3, Float)
1326        try:
1327            q = f(1.0, x=x, y=y)
1328        except Exception, e:
1329            msg = 'Function %s could not be executed:\n%s' %(f, e)
1330            #FIXME: Reconsider this semantics
1331            raise msg
1332
1333        try:
1334            q = array(q).astype(Float)
1335        except:
1336            msg = 'Return value from vector function %s could ' %f
1337            msg += 'not be converted into a Numeric array of floats.\n'
1338            msg += 'Specified function should return either list or array.'
1339            raise msg
1340
1341        #Is this really what we want?
1342        msg = 'Return vector from function %s ' %f
1343        msg += 'must have same lenght as input vectors'
1344        assert len(q) == N, msg
1345
1346    else:
1347        try:
1348            f = float(f)
1349        except:
1350            msg = 'Force field %s must be either a scalar' %f
1351            msg += ' or a vector function'
[2689]1352            raise Exception(msg)
[2598]1353    return f
1354
1355
1356class Wind_stress:
1357    """Apply wind stress to water momentum in terms of
1358    wind speed [m/s] and wind direction [degrees]
1359    """
1360
1361    def __init__(self, *args, **kwargs):
1362        """Initialise windfield from wind speed s [m/s]
1363        and wind direction phi [degrees]
1364
1365        Inputs v and phi can be either scalars or Python functions, e.g.
1366
1367        W = Wind_stress(10, 178)
1368
1369        #FIXME - 'normal' degrees are assumed for now, i.e. the
1370        vector (1,0) has zero degrees.
1371        We may need to convert from 'compass' degrees later on and also
1372        map from True north to grid north.
1373
1374        Arguments can also be Python functions of t,x,y as in
1375
1376        def speed(t,x,y):
1377            ...
1378            return s
1379
1380        def angle(t,x,y):
1381            ...
1382            return phi
1383
1384        where x and y are vectors.
1385
1386        and then pass the functions in
1387
1388        W = Wind_stress(speed, angle)
1389
1390        The instantiated object W can be appended to the list of
1391        forcing_terms as in
1392
1393        Alternatively, one vector valued function for (speed, angle)
1394        can be applied, providing both quantities simultaneously.
1395        As in
1396        W = Wind_stress(F), where returns (speed, angle) for each t.
1397
1398        domain.forcing_terms.append(W)
1399        """
1400
1401        from config import rho_a, rho_w, eta_w
1402        from Numeric import array, Float
1403
1404        if len(args) == 2:
1405            s = args[0]
1406            phi = args[1]
1407        elif len(args) == 1:
1408            #Assume vector function returning (s, phi)(t,x,y)
1409            vector_function = args[0]
1410            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1411            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1412        else:
1413           #Assume info is in 2 keyword arguments
1414
1415           if len(kwargs) == 2:
1416               s = kwargs['s']
1417               phi = kwargs['phi']
1418           else:
1419               raise 'Assumes two keyword arguments: s=..., phi=....'
1420
1421        self.speed = check_forcefield(s)
1422        self.phi = check_forcefield(phi)
1423
1424        self.const = eta_w*rho_a/rho_w
1425
1426
1427    def __call__(self, domain):
1428        """Evaluate windfield based on values found in domain
1429        """
1430
1431        from math import pi, cos, sin, sqrt
1432        from Numeric import Float, ones, ArrayType
1433
1434        xmom_update = domain.quantities['xmomentum'].explicit_update
1435        ymom_update = domain.quantities['ymomentum'].explicit_update
1436
1437        N = domain.number_of_elements
1438        t = domain.time
1439
1440        if callable(self.speed):
1441            xc = domain.get_centroid_coordinates()
1442            s_vec = self.speed(t, xc[:,0], xc[:,1])
1443        else:
1444            #Assume s is a scalar
1445
1446            try:
1447                s_vec = self.speed * ones(N, Float)
1448            except:
1449                msg = 'Speed must be either callable or a scalar: %s' %self.s
1450                raise msg
1451
1452
1453        if callable(self.phi):
1454            xc = domain.get_centroid_coordinates()
1455            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1456        else:
1457            #Assume phi is a scalar
1458
1459            try:
1460                phi_vec = self.phi * ones(N, Float)
1461            except:
1462                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1463                raise msg
1464
1465        assign_windfield_values(xmom_update, ymom_update,
1466                                s_vec, phi_vec, self.const)
1467
1468
1469def assign_windfield_values(xmom_update, ymom_update,
1470                            s_vec, phi_vec, const):
1471    """Python version of assigning wind field to update vectors.
1472    A c version also exists (for speed)
1473    """
1474    from math import pi, cos, sin, sqrt
1475
1476    N = len(s_vec)
1477    for k in range(N):
1478        s = s_vec[k]
1479        phi = phi_vec[k]
1480
1481        #Convert to radians
1482        phi = phi*pi/180
1483
1484        #Compute velocity vector (u, v)
1485        u = s*cos(phi)
1486        v = s*sin(phi)
1487
1488        #Compute wind stress
1489        S = const * sqrt(u**2 + v**2)
1490        xmom_update[k] += S*u
1491        ymom_update[k] += S*v
1492
1493
1494
1495##############################
1496#OBSOLETE STUFF
1497
1498def balance_deep_and_shallow_old(domain):
1499    """Compute linear combination between stage as computed by
1500    gradient-limiters and stage computed as constant height above bed.
1501    The former takes precedence when heights are large compared to the
1502    bed slope while the latter takes precedence when heights are
1503    relatively small.  Anything in between is computed as a balanced
1504    linear combination in order to avoid numerical disturbances which
1505    would otherwise appear as a result of hard switching between
1506    modes.
1507    """
1508
1509    #OBSOLETE
1510
1511    #Shortcuts
1512    wc = domain.quantities['stage'].centroid_values
1513    zc = domain.quantities['elevation'].centroid_values
1514    hc = wc - zc
1515
1516    wv = domain.quantities['stage'].vertex_values
1517    zv = domain.quantities['elevation'].vertex_values
1518    hv = wv-zv
1519
1520
1521    #Computed linear combination between constant stages and and
1522    #stages parallel to the bed elevation.
1523    for k in range(domain.number_of_elements):
1524        #Compute maximal variation in bed elevation
1525        #  This quantitiy is
1526        #    dz = max_i abs(z_i - z_c)
1527        #  and it is independent of dimension
1528        #  In the 1d case zc = (z0+z1)/2
1529        #  In the 2d case zc = (z0+z1+z2)/3
1530
1531        dz = max(abs(zv[k,0]-zc[k]),
1532                 abs(zv[k,1]-zc[k]),
1533                 abs(zv[k,2]-zc[k]))
1534
1535
1536        hmin = min( hv[k,:] )
1537
1538        #Create alpha in [0,1], where alpha==0 means using shallow
1539        #first order scheme and alpha==1 means using the stage w as
1540        #computed by the gradient limiter (1st or 2nd order)
1541        #
1542        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
1543        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
1544
1545        if dz > 0.0:
1546            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
1547        else:
1548            #Flat bed
1549            alpha = 1.0
1550
1551        #Weighted balance between stage parallel to bed elevation
1552        #(wvi = zvi + hc) and stage as computed by 1st or 2nd
1553        #order gradient limiter
1554        #(wvi = zvi + hvi) where i=0,1,2 denotes the vertex ids
1555        #
1556        #It follows that the updated wvi is
1557        #  wvi := (1-alpha)*(zvi+hc) + alpha*(zvi+hvi) =
1558        #  zvi + hc + alpha*(hvi - hc)
1559        #
1560        #Note that hvi = zc+hc-zvi in the first order case (constant).
1561
1562        if alpha < 1:
1563            for i in range(3):
1564                wv[k,i] = zv[k,i] + hc[k] + alpha*(hv[k,i]-hc[k])
1565
1566
1567            #Momentums at centroids
1568            xmomc = domain.quantities['xmomentum'].centroid_values
1569            ymomc = domain.quantities['ymomentum'].centroid_values
1570
1571            #Momentums at vertices
1572            xmomv = domain.quantities['xmomentum'].vertex_values
1573            ymomv = domain.quantities['ymomentum'].vertex_values
1574
1575            # Update momentum as a linear combination of
1576            # xmomc and ymomc (shallow) and momentum
1577            # from extrapolator xmomv and ymomv (deep).
1578            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
1579            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
1580
1581
1582
1583
1584
1585###########################
1586###########################
1587#Geometries
1588
1589
1590#FIXME: Rethink this way of creating values.
1591
1592
1593class Weir:
1594    """Set a bathymetry for weir with a hole and a downstream gutter
1595    x,y are assumed to be in the unit square
1596    """
1597
1598    def __init__(self, stage):
1599        self.inflow_stage = stage
1600
1601    def __call__(self, x, y):
1602        from Numeric import zeros, Float
1603        from math import sqrt
1604
1605        N = len(x)
1606        assert N == len(y)
1607
1608        z = zeros(N, Float)
1609        for i in range(N):
1610            z[i] = -x[i]/2  #General slope
1611
1612            #Flattish bit to the left
1613            if x[i] < 0.3:
1614                z[i] = -x[i]/10
1615
1616            #Weir
1617            if x[i] >= 0.3 and x[i] < 0.4:
1618                z[i] = -x[i]+0.9
1619
1620            #Dip
1621            x0 = 0.6
1622            #depth = -1.3
1623            depth = -1.0
1624            #plateaux = -0.9
1625            plateaux = -0.6
1626            if y[i] < 0.7:
1627                if x[i] > x0 and x[i] < 0.9:
1628                    z[i] = depth
1629
1630                #RHS plateaux
1631                if x[i] >= 0.9:
1632                    z[i] = plateaux
1633
1634
1635            elif y[i] >= 0.7 and y[i] < 1.5:
1636                #Restrict and deepen
1637                if x[i] >= x0 and x[i] < 0.8:
1638                    z[i] = depth-(y[i]/3-0.3)
1639                    #z[i] = depth-y[i]/5
1640                    #z[i] = depth
1641                elif x[i] >= 0.8:
1642                    #RHS plateaux
1643                    z[i] = plateaux
1644
1645            elif y[i] >= 1.5:
1646                if x[i] >= x0 and x[i] < 0.8 + (y[i]-1.5)/1.2:
1647                    #Widen up and stay at constant depth
1648                    z[i] = depth-1.5/5
1649                elif x[i] >= 0.8 + (y[i]-1.5)/1.2:
1650                    #RHS plateaux
1651                    z[i] = plateaux
1652
1653
1654            #Hole in weir (slightly higher than inflow condition)
1655            if x[i] >= 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1656                z[i] = -x[i]+self.inflow_stage + 0.02
1657
1658            #Channel behind weir
1659            x0 = 0.5
1660            if x[i] >= 0.4 and x[i] < x0 and y[i] > 0.2 and y[i] < 0.4:
1661                z[i] = -x[i]+self.inflow_stage + 0.02
1662
1663            if x[i] >= x0 and x[i] < 0.6 and y[i] > 0.2 and y[i] < 0.4:
1664                #Flatten it out towards the end
1665                z[i] = -x0+self.inflow_stage + 0.02 + (x0-x[i])/5
1666
1667            #Hole to the east
1668            x0 = 1.1; y0 = 0.35
1669            #if x[i] < -0.2 and y < 0.5:
1670            if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1671                z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-1.0
1672
1673            #Tiny channel draining hole
1674            if x[i] >= 1.14 and x[i] < 1.2 and y[i] >= 0.4 and y[i] < 0.6:
1675                z[i] = -0.9 #North south
1676
1677            if x[i] >= 0.9 and x[i] < 1.18 and y[i] >= 0.58 and y[i] < 0.65:
1678                z[i] = -1.0 + (x[i]-0.9)/3 #East west
1679
1680
1681
1682            #Stuff not in use
1683
1684            #Upward slope at inlet to the north west
1685            #if x[i] < 0.0: # and y[i] > 0.5:
1686            #    #z[i] = -y[i]+0.5  #-x[i]/2
1687            #    z[i] = x[i]/4 - y[i]**2 + 0.5
1688
1689            #Hole to the west
1690            #x0 = -0.4; y0 = 0.35 # center
1691            #if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
1692            #    z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-0.2
1693
1694
1695
1696
1697
1698        return z/2
1699
1700class Weir_simple:
1701    """Set a bathymetry for weir with a hole and a downstream gutter
1702    x,y are assumed to be in the unit square
1703    """
1704
1705    def __init__(self, stage):
1706        self.inflow_stage = stage
1707
1708    def __call__(self, x, y):
1709        from Numeric import zeros, Float
1710
1711        N = len(x)
1712        assert N == len(y)
1713
1714        z = zeros(N, Float)
1715        for i in range(N):
1716            z[i] = -x[i]  #General slope
1717
1718            #Flat bit to the left
1719            if x[i] < 0.3:
1720                z[i] = -x[i]/10  #General slope
1721
1722            #Weir
1723            if x[i] > 0.3 and x[i] < 0.4:
1724                z[i] = -x[i]+0.9
1725
1726            #Dip
1727            if x[i] > 0.6 and x[i] < 0.9:
1728                z[i] = -x[i]-0.5  #-y[i]/5
1729
1730            #Hole in weir (slightly higher than inflow condition)
1731            if x[i] > 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
1732                z[i] = -x[i]+self.inflow_stage + 0.05
1733
1734
1735        return z/2
1736
1737
1738
1739class Constant_stage:
1740    """Set an initial condition with constant stage
1741    """
1742    def __init__(self, s):
1743        self.s = s
1744
1745    def __call__(self, x, y):
1746        return self.s
1747
1748class Constant_height:
1749    """Set an initial condition with constant water height, e.g
1750    stage s = z+h
1751    """
1752
1753    def __init__(self, W, h):
1754        self.W = W
1755        self.h = h
1756
1757    def __call__(self, x, y):
1758        if self.W is None:
1759            from Numeric import ones, Float
1760            return self.h*ones(len(x), Float)
1761        else:
1762            return self.W(x,y) + self.h
1763
1764
1765
1766
1767def compute_fluxes_python(domain):
1768    """Compute all fluxes and the timestep suitable for all volumes
1769    in domain.
1770
1771    Compute total flux for each conserved quantity using "flux_function"
1772
1773    Fluxes across each edge are scaled by edgelengths and summed up
1774    Resulting flux is then scaled by area and stored in
1775    explicit_update for each of the three conserved quantities
1776    stage, xmomentum and ymomentum
1777
1778    The maximal allowable speed computed by the flux_function for each volume
1779    is converted to a timestep that must not be exceeded. The minimum of
1780    those is computed as the next overall timestep.
1781
1782    Post conditions:
1783      domain.explicit_update is reset to computed flux values
1784      domain.timestep is set to the largest step satisfying all volumes.
1785    """
1786
1787    import sys
1788    from Numeric import zeros, Float
1789
1790    N = domain.number_of_elements
1791
1792    #Shortcuts
1793    Stage = domain.quantities['stage']
1794    Xmom = domain.quantities['xmomentum']
1795    Ymom = domain.quantities['ymomentum']
1796    Bed = domain.quantities['elevation']
1797
1798    #Arrays
1799    stage = Stage.edge_values
1800    xmom =  Xmom.edge_values
1801    ymom =  Ymom.edge_values
1802    bed =   Bed.edge_values
1803
1804    stage_bdry = Stage.boundary_values
1805    xmom_bdry =  Xmom.boundary_values
1806    ymom_bdry =  Ymom.boundary_values
1807
1808    flux = zeros((N,3), Float) #Work array for summing up fluxes
1809
1810    #Loop
1811    timestep = float(sys.maxint)
1812    for k in range(N):
1813
1814        for i in range(3):
1815            #Quantities inside volume facing neighbour i
1816            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
1817            zl = bed[k, i]
1818
1819            #Quantities at neighbour on nearest face
1820            n = domain.neighbours[k,i]
1821            if n < 0:
1822                m = -n-1 #Convert negative flag to index
1823                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
1824                zr = zl #Extend bed elevation to boundary
1825            else:
1826                m = domain.neighbour_edges[k,i]
1827                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
1828                zr = bed[n, m]
1829
1830
1831            #Outward pointing normal vector
1832            normal = domain.normals[k, 2*i:2*i+2]
1833
1834            #Flux computation using provided function
1835            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
1836
1837            flux[k,:] = edgeflux
1838
1839    return flux
1840
1841
1842
1843
1844
1845
1846
1847##############################################
1848#Initialise module
1849
1850
1851from utilities import compile
1852if compile.can_use_C_extension('shallow_water_ext.c'):
1853    #Replace python version with c implementations
1854
1855    from shallow_water_ext import rotate, assign_windfield_values
1856    compute_fluxes = compute_fluxes_c
1857    extrapolate_second_order_sw=extrapolate_second_order_sw_c
1858    gravity = gravity_c
[2648]1859    manning_friction = manning_friction_implicit_c
[2598]1860    h_limiter = h_limiter_c
1861    balance_deep_and_shallow = balance_deep_and_shallow_c
1862    protect_against_infinitesimal_and_negative_heights = protect_against_infinitesimal_and_negative_heights_c
1863
1864
1865    #distribute_to_vertices_and_edges = distribute_to_vertices_and_edges_c #(like MH's)
1866
1867
1868
1869#Optimisation with psyco
1870from config import use_psyco
1871if use_psyco:
1872    try:
1873        import psyco
1874    except:
1875        import os
1876        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1877            pass
1878            #Psyco isn't supported on 64 bit systems, but it doesn't matter
1879        else:
1880            msg = 'WARNING: psyco (speedup) could not import'+\
1881                  ', you may want to consider installing it'
1882            print msg
1883    else:
1884        psyco.bind(Domain.distribute_to_vertices_and_edges)
1885        psyco.bind(Domain.compute_fluxes)
1886
1887if __name__ == "__main__":
1888    pass
1889
1890# Profiling stuff
1891import profile
1892profiler = profile.Profile()
Note: See TracBrowser for help on using the repository browser.