source: anuga_core/source/anuga/shallow_water/shallow_water_domain_kinetic.py @ 4013

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