source: anuga_work/development/shallow_water_1d/shallow_water_1d.py @ 4959

Last change on this file since 4959 was 4959, checked in by steve, 16 years ago

Copied John Jakeman's and Sudi Mungkasi's 1d shallow water code

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