source: inundation/euler/euler.py @ 2152

Last change on this file since 2152 was 1963, checked in by ole, 19 years ago

Comment

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