source: anuga_core/source/anuga/shallow_water/shallow_water_domain.py @ 4685

Last change on this file since 4685 was 4685, checked in by ole, 17 years ago

Implemented optimisation excluding dry cells from flux calculation.
All tests pass and the script run_okushiri_profile.py reports an improvement
in compute_fluxes from 11.25s to 8.58s or 24% faster.
The overall computation was about 40s, so this optimisation improved the
total running time for the problem in question by 7%.

This partially addresses ticket:135

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 72.2 KB
Line 
1"""Finite-volume computations of the shallow water wave equation.
2
3Title: ANGUA shallow_water_domain - 2D triangular domains for finite-volume
4       computations of the shallow water wave equation.
5
6
7Author: Ole Nielsen (Ole.Nielsen@ga.gov.au),
8        Stephen Roberts (Stephen.Roberts@anu.edu.au),
9        Duncan Gray (Duncan.Gray@ga.gov.au), etc
10
11CreationDate: 2004
12
13Description:
14    This module contains a specialisation of class Domain from
15    module domain.py consisting of methods specific to the
16    Shallow Water Wave Equation
17
18    U_t + E_x + G_y = S
19
20    where
21
22    U = [w, uh, vh]
23    E = [uh, u^2h + gh^2/2, uvh]
24    G = [vh, uvh, v^2h + gh^2/2]
25    S represents source terms forcing the system
26    (e.g. gravity, friction, wind stress, ...)
27
28    and _t, _x, _y denote the derivative with respect to t, x and y
29    respectively.
30
31
32    The quantities are
33
34    symbol    variable name    explanation
35    x         x                horizontal distance from origin [m]
36    y         y                vertical distance from origin [m]
37    z         elevation        elevation of bed on which flow is modelled [m]
38    h         height           water height above z [m]
39    w         stage            absolute water level, w = z+h [m]
40    u                          speed in the x direction [m/s]
41    v                          speed in the y direction [m/s]
42    uh        xmomentum        momentum in the x direction [m^2/s]
43    vh        ymomentum        momentum in the y direction [m^2/s]
44
45    eta                        mannings friction coefficient [to appear]
46    nu                         wind stress coefficient [to appear]
47
48    The conserved quantities are w, uh, vh
49
50Reference:
51    Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
52    Christopher Zoppou and Stephen Roberts,
53    Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
54
55    Hydrodynamic modelling of coastal inundation.
56    Nielsen, O., S. Roberts, D. Gray, A. McPherson and A. Hitchman
57    In Zerger, A. and Argent, R.M. (eds) MODSIM 2005 International Congress on
58    Modelling and Simulation. Modelling and Simulation Society of Australia and
59    New Zealand, December 2005, pp. 518-523. ISBN: 0-9758400-2-9.
60    http://www.mssanz.org.au/modsim05/papers/nielsen.pdf
61
62
63SeeAlso:
64    TRAC administration of ANUGA (User Manuals etc) at
65    https://datamining.anu.edu.au/anuga and Subversion repository at
66    $HeadURL: anuga_core/source/anuga/shallow_water/shallow_water_domain.py $
67
68Constraints: See GPL license in the user guide
69Version: 1.0 ($Revision: 4685 $)
70ModifiedBy:
71    $Author: ole $
72    $Date: 2007-08-28 04:23:35 +0000 (Tue, 28 Aug 2007) $
73
74"""
75
76#Subversion keywords:
77#
78#$LastChangedDate: 2007-08-28 04:23:35 +0000 (Tue, 28 Aug 2007) $
79#$LastChangedRevision: 4685 $
80#$LastChangedBy: ole $
81
82from Numeric import zeros, ones, Float, array, sum, size
83from Numeric import compress, arange
84
85
86from anuga.abstract_2d_finite_volumes.domain import Domain as Generic_Domain
87from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
88     import Boundary
89from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
90     import File_boundary
91from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
92     import Dirichlet_boundary
93from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
94     import Time_boundary
95from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
96     import Transmissive_boundary
97
98from anuga.utilities.numerical_tools import gradient, mean
99from anuga.config import minimum_storable_height
100from anuga.config import minimum_allowed_height, maximum_allowed_speed
101from anuga.config import g, beta_h, beta_w, beta_w_dry,\
102     beta_uh, beta_uh_dry, beta_vh, beta_vh_dry, tight_slope_limiters
103from anuga.config import alpha_balance
104from anuga.config import optimise_dry_cells
105
106
107#Shallow water domain
108class Domain(Generic_Domain):
109
110    conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
111    other_quantities = ['elevation', 'friction']
112   
113    def __init__(self,
114                 coordinates=None,
115                 vertices=None,
116                 boundary=None,
117                 tagged_elements=None,
118                 geo_reference=None,
119                 use_inscribed_circle=False,
120                 mesh_filename=None,
121                 use_cache=False,
122                 verbose=False,
123                 full_send_dict=None,
124                 ghost_recv_dict=None,
125                 processor=0,
126                 numproc=1,
127                 number_of_full_nodes=None,
128                 number_of_full_triangles=None):
129
130
131        other_quantities = ['elevation', 'friction']
132        Generic_Domain.__init__(self,
133                                coordinates,
134                                vertices,
135                                boundary,
136                                Domain.conserved_quantities,
137                                Domain.other_quantities,
138                                tagged_elements,
139                                geo_reference,
140                                use_inscribed_circle,
141                                mesh_filename,
142                                use_cache,
143                                verbose,
144                                full_send_dict,
145                                ghost_recv_dict,
146                                processor,
147                                numproc,
148                                number_of_full_nodes=number_of_full_nodes,
149                                number_of_full_triangles=number_of_full_triangles) 
150
151        #self.minimum_allowed_height = minimum_allowed_height
152        #self.H0 = minimum_allowed_height
153        self.set_minimum_allowed_height(minimum_allowed_height)
154       
155        self.maximum_allowed_speed = maximum_allowed_speed
156        self.g = g
157        self.beta_w      = beta_w
158        self.beta_w_dry  = beta_w_dry
159        self.beta_uh     = beta_uh
160        self.beta_uh_dry = beta_uh_dry
161        self.beta_vh     = beta_vh
162        self.beta_vh_dry = beta_vh_dry
163        self.beta_h      = beta_h
164        self.alpha_balance = alpha_balance
165
166        self.tight_slope_limiters = tight_slope_limiters
167        self.optimise_dry_cells = optimise_dry_cells
168
169        self.flux_function = flux_function_central
170        #self.flux_function = flux_function_kinetic
171       
172        self.forcing_terms.append(manning_friction)
173        self.forcing_terms.append(gravity)
174
175        #Stored output
176        self.store = True
177        self.format = 'sww'
178        self.set_store_vertices_uniquely(False)
179        self.minimum_storable_height = minimum_storable_height
180        self.quantities_to_be_stored = ['stage','xmomentum','ymomentum']
181               
182
183    def set_all_limiters(self, beta):
184        """Shorthand to assign one constant value [0,1[ to all limiters.
185        0 Corresponds to first order, where as larger values make use of
186        the second order scheme.
187        """
188
189        self.beta_w      = beta
190        self.beta_w_dry  = beta
191        self.beta_uh     = beta
192        self.beta_uh_dry = beta
193        self.beta_vh     = beta
194        self.beta_vh_dry = beta
195        self.beta_h      = beta
196       
197
198    def set_store_vertices_uniquely(self, flag, reduction=None):
199        """Decide whether vertex values should be stored uniquely as
200        computed in the model or whether they should be reduced to one
201        value per vertex using self.reduction.
202        """
203
204        # FIXME (Ole): how about using the word continuous vertex values?
205        self.smooth = not flag
206
207        #Reduction operation for get_vertex_values
208        if reduction is None:
209            self.reduction = mean
210            #self.reduction = min  #Looks better near steep slopes
211
212
213    def set_minimum_storable_height(self, minimum_storable_height):
214        """
215        Set the minimum depth that will be recognised when writing
216        to an sww file. This is useful for removing thin water layers
217        that seems to be caused by friction creep.
218
219        The minimum allowed sww depth is in meters.
220        """
221        self.minimum_storable_height = minimum_storable_height
222
223
224    def set_minimum_allowed_height(self, minimum_allowed_height):
225        """
226        Set the minimum depth that will be recognised in the numerical
227        scheme
228
229        The minimum allowed depth is in meters.
230
231        The parameter H0 (Minimal height for flux computation)
232        is also set by this function
233        """
234
235        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
236        #rename tight_slope_limiters to tight_slope_limiters.
237        #Maybe use histogram to identify isolated extreme speeds and deal with them adaptively
238        #similarly to how we used to use 1 order steps to recover.
239        self.minimum_allowed_height = minimum_allowed_height
240        self.H0 = minimum_allowed_height   
241       
242
243    def set_maximum_allowed_speed(self, maximum_allowed_speed):
244        """
245        Set the maximum particle speed that is allowed in water
246        shallower than minimum_allowed_height. This is useful for
247        controlling speeds in very thin layers of water and at the same time
248        allow some movement avoiding pooling of water.
249
250        """
251        self.maximum_allowed_speed = maximum_allowed_speed
252
253
254    def set_points_file_block_line_size(self,points_file_block_line_size):
255        """
256        Set the minimum depth that will be recognised when writing
257        to an sww file. This is useful for removing thin water layers
258        that seems to be caused by friction creep.
259
260        The minimum allowed sww depth is in meters.
261        """
262        self.points_file_block_line_size = points_file_block_line_size
263       
264    def set_quantities_to_be_stored(self, q):
265        """Specify which quantities will be stored in the sww file.
266
267        q must be either:
268          - the name of a quantity
269          - a list of quantity names
270          - None
271
272        In the two first cases, the named quantities will be stored at
273        each yieldstep (This is in addition to the quantities elevation
274        and friction)
275       
276        If q is None, storage will be switched off altogether.
277        """
278
279
280        if q is None:
281            self.quantities_to_be_stored = []
282            self.store = False
283            return
284
285        if isinstance(q, basestring):
286            q = [q] # Turn argument into a list
287
288        #Check correcness
289        for quantity_name in q:
290            msg = 'Quantity %s is not a valid conserved quantity'\
291                  %quantity_name
292           
293            assert quantity_name in self.conserved_quantities, msg
294
295        self.quantities_to_be_stored = q
296
297
298
299    def get_wet_elements(self, indices=None):
300        """Return indices for elements where h > minimum_allowed_height
301
302        Optional argument:
303            indices is the set of element ids that the operation applies to.
304
305        Usage:
306            indices = get_wet_elements()
307
308        Note, centroid values are used for this operation           
309        """
310
311        # Water depth below which it is considered to be 0 in the model
312        # FIXME (Ole): Allow this to be specified as a keyword argument as well
313        from anuga.config import minimum_allowed_height
314
315       
316        elevation = self.get_quantity('elevation').\
317                    get_values(location='centroids', indices=indices)
318        stage = self.get_quantity('stage').\
319                get_values(location='centroids', indices=indices)       
320        depth = stage - elevation
321
322        # Select indices for which depth > 0
323        wet_indices = compress(depth > minimum_allowed_height,
324                               arange(len(depth)))
325        return wet_indices
326
327
328    def get_maximum_inundation_elevation(self, indices=None):
329        """Return highest elevation where h > 0
330
331        Optional argument:
332            indices is the set of element ids that the operation applies to.
333
334        Usage:
335            q = get_maximum_inundation_elevation()
336
337        Note, centroid values are used for this operation           
338        """
339
340        wet_elements = self.get_wet_elements(indices)
341        return self.get_quantity('elevation').\
342               get_maximum_value(indices=wet_elements)
343
344
345    def get_maximum_inundation_location(self, indices=None):
346        """Return location of highest elevation where h > 0
347
348        Optional argument:
349            indices is the set of element ids that the operation applies to.
350
351        Usage:
352            q = get_maximum_inundation_location()
353
354        Note, centroid values are used for this operation           
355        """
356
357        wet_elements = self.get_wet_elements(indices)
358        return self.get_quantity('elevation').\
359               get_maximum_location(indices=wet_elements)   
360
361    def check_integrity(self):
362        Generic_Domain.check_integrity(self)
363
364        #Check that we are solving the shallow water wave equation
365
366        msg = 'First conserved quantity must be "stage"'
367        assert self.conserved_quantities[0] == 'stage', msg
368        msg = 'Second conserved quantity must be "xmomentum"'
369        assert self.conserved_quantities[1] == 'xmomentum', msg
370        msg = 'Third conserved quantity must be "ymomentum"'
371        assert self.conserved_quantities[2] == 'ymomentum', msg
372
373    def extrapolate_second_order_sw(self):
374        #Call correct module function
375        #(either from this module or C-extension)
376        extrapolate_second_order_sw(self)
377
378    def compute_fluxes(self):
379        #Call correct module function
380        #(either from this module or C-extension)
381        compute_fluxes(self)
382
383    def distribute_to_vertices_and_edges(self):
384        #Call correct module function
385        #(either from this module or C-extension)
386        distribute_to_vertices_and_edges(self)
387
388
389    #FIXME: Under construction
390#     def set_defaults(self):
391#         """Set default values for uninitialised quantities.
392#         This is specific to the shallow water wave equation
393#         Defaults for 'elevation', 'friction', 'xmomentum' and 'ymomentum'
394#         are 0.0. Default for 'stage' is whatever the value of 'elevation'.
395#         """
396
397#         for name in self.other_quantities + self.conserved_quantities:
398#             print name
399#             print self.quantities.keys()
400#             if not self.quantities.has_key(name):
401#                 if name == 'stage':
402
403#                     if self.quantities.has_key('elevation'):
404#                         z = self.quantities['elevation'].vertex_values
405#                         self.set_quantity(name, z)
406#                     else:
407#                         self.set_quantity(name, 0.0)
408#                 else:
409#                     self.set_quantity(name, 0.0)
410
411
412
413#         #Lift negative heights up
414#         #z = self.quantities['elevation'].vertex_values
415#         #w = self.quantities['stage'].vertex_values
416
417#         #h = w-z
418
419#         #for k in range(h.shape[0]):
420#         #    for i in range(3):
421#         #        if h[k, i] < 0.0:
422#         #            w[k, i] = z[k, i]
423
424
425#         #self.quantities['stage'].interpolate()
426
427
428    def evolve(self,
429               yieldstep = None,
430               finaltime = None,
431               duration = None,
432               skip_initial_step = False):
433        """Specialisation of basic evolve method from parent class
434        """
435
436        #Call check integrity here rather than from user scripts
437        #self.check_integrity()
438
439        msg = 'Parameter beta_h must be in the interval [0, 1['
440        assert 0 <= self.beta_h <= 1.0, msg
441        msg = 'Parameter beta_w must be in the interval [0, 1['
442        assert 0 <= self.beta_w <= 1.0, msg
443
444
445        #Initial update of vertex and edge values before any storage
446        #and or visualisation
447        self.distribute_to_vertices_and_edges()
448
449        if self.store is True and self.time == 0.0:
450            self.initialise_storage()
451            #print 'Storing results in ' + self.writer.filename
452        else:
453            pass
454            #print 'Results will not be stored.'
455            #print 'To store results set domain.store = True'
456            #FIXME: Diagnostic output should be controlled by
457            # a 'verbose' flag living in domain (or in a parent class)
458
459        #Call basic machinery from parent class
460        for t in Generic_Domain.evolve(self,
461                                       yieldstep=yieldstep,
462                                       finaltime=finaltime,
463                                       duration=duration,
464                                       skip_initial_step=skip_initial_step):
465
466            #Store model data, e.g. for subsequent visualisation
467            if self.store is True:
468                self.store_timestep(self.quantities_to_be_stored)
469
470            #FIXME: Could maybe be taken from specified list
471            #of 'store every step' quantities
472
473            #Pass control on to outer loop for more specific actions
474            yield(t)
475
476    def initialise_storage(self):
477        """Create and initialise self.writer object for storing data.
478        Also, save x,y and bed elevation
479        """
480
481        from anuga.shallow_water.data_manager import get_dataobject
482
483        #Initialise writer
484        self.writer = get_dataobject(self, mode = 'w')
485
486        #Store vertices and connectivity
487        self.writer.store_connectivity()
488
489
490    def store_timestep(self, name):
491        """Store named quantity and time.
492
493        Precondition:
494           self.write has been initialised
495        """
496        self.writer.store_timestep(name)
497
498
499#=============== End of Shallow Water Domain ===============================
500
501
502
503#Rotation of momentum vector
504def rotate(q, normal, direction = 1):
505    """Rotate the momentum component q (q[1], q[2])
506    from x,y coordinates to coordinates based on normal vector.
507
508    If direction is negative the rotation is inverted.
509
510    Input vector is preserved
511
512    This function is specific to the shallow water wave equation
513    """
514
515    assert len(q) == 3,\
516           'Vector of conserved quantities must have length 3'\
517           'for 2D shallow water equation'
518
519    try:
520        l = len(normal)
521    except:
522        raise 'Normal vector must be an Numeric array'
523
524    assert l == 2, 'Normal vector must have 2 components'
525
526
527    n1 = normal[0]
528    n2 = normal[1]
529
530    r = zeros(len(q), Float) #Rotated quantities
531    r[0] = q[0]              #First quantity, height, is not rotated
532
533    if direction == -1:
534        n2 = -n2
535
536
537    r[1] =  n1*q[1] + n2*q[2]
538    r[2] = -n2*q[1] + n1*q[2]
539
540    return r
541
542
543
544####################################
545# Flux computation
546def flux_function_central(normal, ql, qr, zl, zr):
547    """Compute fluxes between volumes for the shallow water wave equation
548    cast in terms of w = h+z using the 'central scheme' as described in
549
550    Kurganov, Noelle, Petrova. 'Semidiscrete Central-Upwind Schemes For
551    Hyperbolic Conservation Laws and Hamilton-Jacobi Equations'.
552    Siam J. Sci. Comput. Vol. 23, No. 3, pp. 707-740.
553
554    The implemented formula is given in equation (3.15) on page 714
555
556    Conserved quantities w, uh, vh are stored as elements 0, 1 and 2
557    in the numerical vectors ql and qr.
558
559    Bed elevations zl and zr.
560    """
561
562    from anuga.config import g, epsilon
563    from math import sqrt
564
565    #Align momentums with x-axis
566    q_left  = rotate(ql, normal, direction = 1)
567    q_right = rotate(qr, normal, direction = 1)
568
569    z = (zl+zr)/2 #Take average of field values
570
571    w_left  = q_left[0]   #w=h+z
572    h_left  = w_left-z
573    uh_left = q_left[1]
574
575    if h_left < epsilon:
576        u_left = 0.0  #Could have been negative
577        h_left = 0.0
578    else:
579        u_left  = uh_left/h_left
580
581
582    w_right  = q_right[0]  #w=h+z
583    h_right  = w_right-z
584    uh_right = q_right[1]
585
586
587    if h_right < epsilon:
588        u_right = 0.0  #Could have been negative
589        h_right = 0.0
590    else:
591        u_right  = uh_right/h_right
592
593    vh_left  = q_left[2]
594    vh_right = q_right[2]
595
596    soundspeed_left  = sqrt(g*h_left)
597    soundspeed_right = sqrt(g*h_right)
598
599    #Maximal wave speed
600    s_max = max(u_left+soundspeed_left, u_right+soundspeed_right, 0)
601
602    #Minimal wave speed
603    s_min = min(u_left-soundspeed_left, u_right-soundspeed_right, 0)
604
605    #Flux computation
606
607    #FIXME(Ole): Why is it again that we don't
608    #use uh_left and uh_right directly in the first entries?
609    flux_left  = array([u_left*h_left,
610                        u_left*uh_left + 0.5*g*h_left**2,
611                        u_left*vh_left])
612    flux_right = array([u_right*h_right,
613                        u_right*uh_right + 0.5*g*h_right**2,
614                        u_right*vh_right])
615
616    denom = s_max-s_min
617    if denom == 0.0:
618        edgeflux = array([0.0, 0.0, 0.0])
619        max_speed = 0.0
620    else:
621        edgeflux = (s_max*flux_left - s_min*flux_right)/denom
622        edgeflux += s_max*s_min*(q_right-q_left)/denom
623
624        edgeflux = rotate(edgeflux, normal, direction=-1)
625        max_speed = max(abs(s_max), abs(s_min))
626
627    return edgeflux, max_speed
628
629def erfcc(x):
630
631    from math import fabs, exp
632
633    z=fabs(x)
634    t=1.0/(1.0+0.5*z)
635    result=t*exp(-z*z-1.26551223+t*(1.00002368+t*(.37409196+
636         t*(.09678418+t*(-.18628806+t*(.27886807+t*(-1.13520398+
637         t*(1.48851587+t*(-.82215223+t*.17087277)))))))))
638    if x < 0.0:
639        result = 2.0-result
640
641    return result
642
643def flux_function_kinetic(normal, ql, qr, zl, zr):
644    """Compute fluxes between volumes for the shallow water wave equation
645    cast in terms of w = h+z using the 'central scheme' as described in
646
647    Zhang et. al., Advances in Water Resources, 26(6), 2003, 635-647.
648
649
650    Conserved quantities w, uh, vh are stored as elements 0, 1 and 2
651    in the numerical vectors ql an qr.
652
653    Bed elevations zl and zr.
654    """
655
656    from anuga.config import g, epsilon
657    from math import sqrt
658    from Numeric import array
659
660    #Align momentums with x-axis
661    q_left  = rotate(ql, normal, direction = 1)
662    q_right = rotate(qr, normal, direction = 1)
663
664    z = (zl+zr)/2 #Take average of field values
665
666    w_left  = q_left[0]   #w=h+z
667    h_left  = w_left-z
668    uh_left = q_left[1]
669
670    if h_left < epsilon:
671        u_left = 0.0  #Could have been negative
672        h_left = 0.0
673    else:
674        u_left  = uh_left/h_left
675
676
677    w_right  = q_right[0]  #w=h+z
678    h_right  = w_right-z
679    uh_right = q_right[1]
680
681
682    if h_right < epsilon:
683        u_right = 0.0  #Could have been negative
684        h_right = 0.0
685    else:
686        u_right  = uh_right/h_right
687
688    vh_left  = q_left[2]
689    vh_right = q_right[2]
690
691    soundspeed_left  = sqrt(g*h_left)
692    soundspeed_right = sqrt(g*h_right)
693
694    #Maximal wave speed
695    s_max = max(u_left+soundspeed_left, u_right+soundspeed_right, 0)
696
697    #Minimal wave speed
698    s_min = min(u_left-soundspeed_left, u_right-soundspeed_right, 0)
699
700    #Flux computation
701
702    F_left  = 0.0
703    F_right = 0.0
704    from math import sqrt, pi, exp
705    if h_left > 0.0:
706        F_left = u_left/sqrt(g*h_left)
707    if h_right > 0.0:
708        F_right = u_right/sqrt(g*h_right)
709
710    edgeflux = array([0.0, 0.0, 0.0])
711
712    edgeflux[0] = h_left*u_left/2.0*erfcc(-F_left) +  \
713          h_left*sqrt(g*h_left)/2.0/sqrt(pi)*exp(-(F_left**2)) + \
714          h_right*u_right/2.0*erfcc(F_right) -  \
715          h_right*sqrt(g*h_right)/2.0/sqrt(pi)*exp(-(F_right**2))
716
717    edgeflux[1] = (h_left*u_left**2 + g/2.0*h_left**2)/2.0*erfcc(-F_left) + \
718          u_left*h_left*sqrt(g*h_left)/2.0/sqrt(pi)*exp(-(F_left**2)) + \
719          (h_right*u_right**2 + g/2.0*h_right**2)/2.0*erfcc(F_right) -  \
720          u_right*h_right*sqrt(g*h_right)/2.0/sqrt(pi)*exp(-(F_right**2))
721
722    edgeflux[2] = vh_left*u_left/2.0*erfcc(-F_left) + \
723          vh_left*sqrt(g*h_left)/2.0/sqrt(pi)*exp(-(F_left**2)) + \
724          vh_right*u_right/2.0*erfcc(F_right) - \
725          vh_right*sqrt(g*h_right)/2.0/sqrt(pi)*exp(-(F_right**2))
726
727
728    edgeflux = rotate(edgeflux, normal, direction=-1)
729    max_speed = max(abs(s_max), abs(s_min))
730
731    return edgeflux, max_speed
732
733
734
735def compute_fluxes(domain):
736    """Compute all fluxes and the timestep suitable for all volumes
737    in domain.
738
739    Compute total flux for each conserved quantity using "flux_function"
740
741    Fluxes across each edge are scaled by edgelengths and summed up
742    Resulting flux is then scaled by area and stored in
743    explicit_update for each of the three conserved quantities
744    stage, xmomentum and ymomentum
745
746    The maximal allowable speed computed by the flux_function for each volume
747    is converted to a timestep that must not be exceeded. The minimum of
748    those is computed as the next overall timestep.
749
750    Post conditions:
751      domain.explicit_update is reset to computed flux values
752      domain.timestep is set to the largest step satisfying all volumes.
753    """
754
755    import sys
756
757    N = len(domain) # number_of_triangles
758
759    #Shortcuts
760    Stage = domain.quantities['stage']
761    Xmom = domain.quantities['xmomentum']
762    Ymom = domain.quantities['ymomentum']
763    Bed = domain.quantities['elevation']
764
765    #Arrays
766    stage = Stage.edge_values
767    xmom =  Xmom.edge_values
768    ymom =  Ymom.edge_values
769    bed =   Bed.edge_values
770
771    stage_bdry = Stage.boundary_values
772    xmom_bdry =  Xmom.boundary_values
773    ymom_bdry =  Ymom.boundary_values
774
775    flux = zeros(3, Float) #Work array for summing up fluxes
776
777
778    #Loop
779    timestep = float(sys.maxint)
780    for k in range(N):
781
782        flux[:] = 0.  #Reset work array
783        for i in range(3):
784            #Quantities inside volume facing neighbour i
785            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
786            zl = bed[k, i]
787
788            #Quantities at neighbour on nearest face
789            n = domain.neighbours[k,i]
790            if n < 0:
791                m = -n-1 #Convert negative flag to index
792                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
793                zr = zl #Extend bed elevation to boundary
794            else:
795                m = domain.neighbour_edges[k,i]
796                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
797                zr = bed[n, m]
798
799
800            #Outward pointing normal vector
801            normal = domain.normals[k, 2*i:2*i+2]
802
803            #Flux computation using provided function
804            edgeflux, max_speed = domain.flux_function(normal, ql, qr, zl, zr)
805            flux -= edgeflux * domain.edgelengths[k,i]
806
807            #Update optimal_timestep on full cells
808            if  domain.tri_full_flag[k] == 1:
809                try:
810                    timestep = min(timestep, 0.5*domain.radii[k]/max_speed)
811                except ZeroDivisionError:
812                    pass
813
814        #Normalise by area and store for when all conserved
815        #quantities get updated
816        flux /= domain.areas[k]
817        Stage.explicit_update[k] = flux[0]
818        Xmom.explicit_update[k] = flux[1]
819        Ymom.explicit_update[k] = flux[2]
820        domain.max_speed[k] = max_speed
821
822
823    domain.timestep = timestep
824
825#MH090605 The following method belongs to the shallow_water domain class
826#see comments in the corresponding method in shallow_water_ext.c
827def extrapolate_second_order_sw_c(domain):
828    """Wrapper calling C version of extrapolate_second_order_sw
829    """
830    import sys
831
832    N = len(domain) # number_of_triangles
833
834    #Shortcuts
835    Stage = domain.quantities['stage']
836    Xmom = domain.quantities['xmomentum']
837    Ymom = domain.quantities['ymomentum']
838    Elevation = domain.quantities['elevation']
839    from shallow_water_ext import extrapolate_second_order_sw
840    extrapolate_second_order_sw(domain,
841                                domain.surrogate_neighbours,
842                                domain.number_of_boundaries,
843                                domain.centroid_coordinates,
844                                Stage.centroid_values,
845                                Xmom.centroid_values,
846                                Ymom.centroid_values,
847                                Elevation.centroid_values,
848                                domain.vertex_coordinates,
849                                Stage.vertex_values,
850                                Xmom.vertex_values,
851                                Ymom.vertex_values,
852                                Elevation.vertex_values)
853
854def compute_fluxes_c(domain):
855    """Wrapper calling C version of compute fluxes
856    """
857
858    import sys
859
860    N = len(domain) # number_of_triangles
861
862    #Shortcuts
863    Stage = domain.quantities['stage']
864    Xmom = domain.quantities['xmomentum']
865    Ymom = domain.quantities['ymomentum']
866    Bed = domain.quantities['elevation']
867
868    timestep = float(sys.maxint)
869    from shallow_water_ext import\
870         compute_fluxes_ext_central as compute_fluxes_ext
871
872    domain.timestep = compute_fluxes_ext(timestep, domain.epsilon,
873                                         domain.H0,
874                                         domain.g,
875                                         domain.neighbours,
876                                         domain.neighbour_edges,
877                                         domain.normals,
878                                         domain.edgelengths,
879                                         domain.radii,
880                                         domain.areas,
881                                         domain.tri_full_flag,
882                                         Stage.edge_values,
883                                         Xmom.edge_values,
884                                         Ymom.edge_values,
885                                         Bed.edge_values,
886                                         Stage.boundary_values,
887                                         Xmom.boundary_values,
888                                         Ymom.boundary_values,
889                                         Stage.explicit_update,
890                                         Xmom.explicit_update,
891                                         Ymom.explicit_update,
892                                         domain.already_computed_flux,
893                                         domain.max_speed,
894                                         int(domain.optimise_dry_cells))
895
896
897####################################
898# Module functions for gradient limiting (distribute_to_vertices_and_edges)
899
900def distribute_to_vertices_and_edges(domain):
901    """Distribution from centroids to vertices specific to the
902    shallow water wave
903    equation.
904
905    It will ensure that h (w-z) is always non-negative even in the
906    presence of steep bed-slopes by taking a weighted average between shallow
907    and deep cases.
908
909    In addition, all conserved quantities get distributed as per either a
910    constant (order==1) or a piecewise linear function (order==2).
911
912    FIXME: more explanation about removal of artificial variability etc
913
914    Precondition:
915      All quantities defined at centroids and bed elevation defined at
916      vertices.
917
918    Postcondition
919      Conserved quantities defined at vertices
920
921    """
922
923    from anuga.config import optimised_gradient_limiter
924
925    #Remove very thin layers of water
926    protect_against_infinitesimal_and_negative_heights(domain)
927
928    #Extrapolate all conserved quantities
929    if optimised_gradient_limiter:
930        #MH090605 if second order,
931        #perform the extrapolation and limiting on
932        #all of the conserved quantitie
933
934        if (domain._order_ == 1):
935            for name in domain.conserved_quantities:
936                Q = domain.quantities[name]
937                Q.extrapolate_first_order()
938        elif domain._order_ == 2:
939            domain.extrapolate_second_order_sw()
940        else:
941            raise 'Unknown order'
942    else:
943        #old code:
944        for name in domain.conserved_quantities:
945            Q = domain.quantities[name]
946            if domain._order_ == 1:
947                Q.extrapolate_first_order()
948            elif domain._order_ == 2:
949                Q.extrapolate_second_order()
950                Q.limit()
951            else:
952                raise 'Unknown order'
953
954
955    #Take bed elevation into account when water heights are small
956    balance_deep_and_shallow(domain)
957
958    #Compute edge values by interpolation
959    for name in domain.conserved_quantities:
960        Q = domain.quantities[name]
961        Q.interpolate_from_vertices_to_edges()
962
963
964def protect_against_infinitesimal_and_negative_heights(domain):
965    """Protect against infinitesimal heights and associated high velocities
966    """
967
968    #Shortcuts
969    wc = domain.quantities['stage'].centroid_values
970    zc = domain.quantities['elevation'].centroid_values
971    xmomc = domain.quantities['xmomentum'].centroid_values
972    ymomc = domain.quantities['ymomentum'].centroid_values
973    hc = wc - zc  #Water depths at centroids
974
975    #Update
976    #FIXME: Modify according to c-version - or discard altogether.
977    for k in range(len(domain)):
978
979        if hc[k] < domain.minimum_allowed_height:
980            #Control stage
981            if hc[k] < domain.epsilon:
982                wc[k] = zc[k] # Contain 'lost mass' error
983
984            #Control momentum
985            xmomc[k] = ymomc[k] = 0.0
986
987
988def protect_against_infinitesimal_and_negative_heights_c(domain):
989    """Protect against infinitesimal heights and associated high velocities
990    """
991
992    #Shortcuts
993    wc = domain.quantities['stage'].centroid_values
994    zc = domain.quantities['elevation'].centroid_values
995    xmomc = domain.quantities['xmomentum'].centroid_values
996    ymomc = domain.quantities['ymomentum'].centroid_values
997
998    from shallow_water_ext import protect
999
1000    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
1001            domain.epsilon, wc, zc, xmomc, ymomc)
1002
1003
1004
1005def h_limiter(domain):
1006    """Limit slopes for each volume to eliminate artificial variance
1007    introduced by e.g. second order extrapolator
1008
1009    limit on h = w-z
1010
1011    This limiter depends on two quantities (w,z) so it resides within
1012    this module rather than within quantity.py
1013    """
1014
1015    N = len(domain)
1016    beta_h = domain.beta_h
1017
1018    #Shortcuts
1019    wc = domain.quantities['stage'].centroid_values
1020    zc = domain.quantities['elevation'].centroid_values
1021    hc = wc - zc
1022
1023    wv = domain.quantities['stage'].vertex_values
1024    zv = domain.quantities['elevation'].vertex_values
1025    hv = wv-zv
1026
1027    hvbar = zeros(hv.shape, Float) #h-limited values
1028
1029    #Find min and max of this and neighbour's centroid values
1030    hmax = zeros(hc.shape, Float)
1031    hmin = zeros(hc.shape, Float)
1032
1033    for k in range(N):
1034        hmax[k] = hmin[k] = hc[k]
1035        for i in range(3):
1036            n = domain.neighbours[k,i]
1037            if n >= 0:
1038                hn = hc[n] #Neighbour's centroid value
1039
1040                hmin[k] = min(hmin[k], hn)
1041                hmax[k] = max(hmax[k], hn)
1042
1043
1044    #Diffences between centroids and maxima/minima
1045    dhmax = hmax - hc
1046    dhmin = hmin - hc
1047
1048    #Deltas between vertex and centroid values
1049    dh = zeros(hv.shape, Float)
1050    for i in range(3):
1051        dh[:,i] = hv[:,i] - hc
1052
1053    #Phi limiter
1054    for k in range(N):
1055
1056        #Find the gradient limiter (phi) across vertices
1057        phi = 1.0
1058        for i in range(3):
1059            r = 1.0
1060            if (dh[k,i] > 0): r = dhmax[k]/dh[k,i]
1061            if (dh[k,i] < 0): r = dhmin[k]/dh[k,i]
1062
1063            phi = min( min(r*beta_h, 1), phi )
1064
1065        #Then update using phi limiter
1066        for i in range(3):
1067            hvbar[k,i] = hc[k] + phi*dh[k,i]
1068
1069    return hvbar
1070
1071
1072
1073def h_limiter_c(domain):
1074    """Limit slopes for each volume to eliminate artificial variance
1075    introduced by e.g. second order extrapolator
1076
1077    limit on h = w-z
1078
1079    This limiter depends on two quantities (w,z) so it resides within
1080    this module rather than within quantity.py
1081
1082    Wrapper for c-extension
1083    """
1084
1085    N = len(domain) # number_of_triangles
1086    beta_h = domain.beta_h
1087
1088    #Shortcuts
1089    wc = domain.quantities['stage'].centroid_values
1090    zc = domain.quantities['elevation'].centroid_values
1091    hc = wc - zc
1092
1093    wv = domain.quantities['stage'].vertex_values
1094    zv = domain.quantities['elevation'].vertex_values
1095    hv = wv - zv
1096
1097    #Call C-extension
1098    from shallow_water_ext import h_limiter_sw as h_limiter
1099    hvbar = h_limiter(domain, hc, hv)
1100
1101    return hvbar
1102
1103
1104def balance_deep_and_shallow(domain):
1105    """Compute linear combination between stage as computed by
1106    gradient-limiters limiting using w, and stage computed by
1107    gradient-limiters limiting using h (h-limiter).
1108    The former takes precedence when heights are large compared to the
1109    bed slope while the latter takes precedence when heights are
1110    relatively small.  Anything in between is computed as a balanced
1111    linear combination in order to avoid numerical disturbances which
1112    would otherwise appear as a result of hard switching between
1113    modes.
1114
1115    The h-limiter is always applied irrespective of the order.
1116    """
1117
1118    #Shortcuts
1119    wc = domain.quantities['stage'].centroid_values
1120    zc = domain.quantities['elevation'].centroid_values
1121    hc = wc - zc
1122
1123    wv = domain.quantities['stage'].vertex_values
1124    zv = domain.quantities['elevation'].vertex_values
1125    hv = wv-zv
1126
1127    #Limit h
1128    hvbar = h_limiter(domain)
1129
1130    for k in range(len(domain)):
1131        #Compute maximal variation in bed elevation
1132        #  This quantitiy is
1133        #    dz = max_i abs(z_i - z_c)
1134        #  and it is independent of dimension
1135        #  In the 1d case zc = (z0+z1)/2
1136        #  In the 2d case zc = (z0+z1+z2)/3
1137
1138        dz = max(abs(zv[k,0]-zc[k]),
1139                 abs(zv[k,1]-zc[k]),
1140                 abs(zv[k,2]-zc[k]))
1141
1142
1143        hmin = min( hv[k,:] )
1144
1145        #Create alpha in [0,1], where alpha==0 means using the h-limited
1146        #stage and alpha==1 means using the w-limited stage as
1147        #computed by the gradient limiter (both 1st or 2nd order)
1148
1149        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
1150        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
1151
1152        if dz > 0.0:
1153            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
1154        else:
1155            #Flat bed
1156            alpha = 1.0
1157
1158        #Let
1159        #
1160        #  wvi be the w-limited stage (wvi = zvi + hvi)
1161        #  wvi- be the h-limited state (wvi- = zvi + hvi-)
1162        #
1163        #
1164        #where i=0,1,2 denotes the vertex ids
1165        #
1166        #Weighted balance between w-limited and h-limited stage is
1167        #
1168        #  wvi := (1-alpha)*(zvi+hvi-) + alpha*(zvi+hvi)
1169        #
1170        #It follows that the updated wvi is
1171        #  wvi := zvi + (1-alpha)*hvi- + alpha*hvi
1172        #
1173        # Momentum is balanced between constant and limited
1174
1175
1176        #for i in range(3):
1177        #    wv[k,i] = zv[k,i] + hvbar[k,i]
1178
1179        #return
1180
1181        if alpha < 1:
1182
1183            for i in range(3):
1184                wv[k,i] = zv[k,i] + (1-alpha)*hvbar[k,i] + alpha*hv[k,i]
1185
1186            #Momentums at centroids
1187            xmomc = domain.quantities['xmomentum'].centroid_values
1188            ymomc = domain.quantities['ymomentum'].centroid_values
1189
1190            #Momentums at vertices
1191            xmomv = domain.quantities['xmomentum'].vertex_values
1192            ymomv = domain.quantities['ymomentum'].vertex_values
1193
1194            # Update momentum as a linear combination of
1195            # xmomc and ymomc (shallow) and momentum
1196            # from extrapolator xmomv and ymomv (deep).
1197            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
1198            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
1199
1200
1201def balance_deep_and_shallow_c(domain):
1202    """Wrapper for C implementation
1203    """
1204
1205    #Shortcuts
1206    wc = domain.quantities['stage'].centroid_values
1207    zc = domain.quantities['elevation'].centroid_values
1208    hc = wc - zc
1209
1210    wv = domain.quantities['stage'].vertex_values
1211    zv = domain.quantities['elevation'].vertex_values
1212    hv = wv - zv
1213
1214    #Momentums at centroids
1215    xmomc = domain.quantities['xmomentum'].centroid_values
1216    ymomc = domain.quantities['ymomentum'].centroid_values
1217
1218    #Momentums at vertices
1219    xmomv = domain.quantities['xmomentum'].vertex_values
1220    ymomv = domain.quantities['ymomentum'].vertex_values
1221
1222    #Limit h
1223    if domain.beta_h > 0:
1224        hvbar = h_limiter(domain)
1225    else:
1226        # print 'Using first order h-limiter'
1227     
1228       
1229        #This is how one would make a first order h_limited value
1230        #as in the old balancer (pre 17 Feb 2005):
1231        # If we wish to hard wire this, one should modify the C-code
1232        from Numeric import zeros, Float
1233        hvbar = zeros( (len(hc), 3), Float)
1234        for i in range(3):
1235            hvbar[:,i] = hc[:]
1236
1237    from shallow_water_ext import balance_deep_and_shallow
1238    balance_deep_and_shallow(domain, wc, zc, hc, wv, zv, hv, hvbar,
1239                             xmomc, ymomc, xmomv, ymomv)
1240
1241
1242
1243
1244###############################################
1245#Boundaries - specific to the shallow water wave equation
1246class Reflective_boundary(Boundary):
1247    """Reflective boundary returns same conserved quantities as
1248    those present in its neighbour volume but reflected.
1249
1250    This class is specific to the shallow water equation as it
1251    works with the momentum quantities assumed to be the second
1252    and third conserved quantities.
1253    """
1254
1255    def __init__(self, domain = None):
1256        Boundary.__init__(self)
1257
1258        if domain is None:
1259            msg = 'Domain must be specified for reflective boundary'
1260            raise msg
1261
1262        #Handy shorthands
1263        self.stage   = domain.quantities['stage'].edge_values
1264        self.xmom    = domain.quantities['xmomentum'].edge_values
1265        self.ymom    = domain.quantities['ymomentum'].edge_values
1266        self.normals = domain.normals
1267
1268        self.conserved_quantities = zeros(3, Float)
1269
1270    def __repr__(self):
1271        return 'Reflective_boundary'
1272
1273
1274    def evaluate(self, vol_id, edge_id):
1275        """Reflective boundaries reverses the outward momentum
1276        of the volume they serve.
1277        """
1278
1279        q = self.conserved_quantities
1280        q[0] = self.stage[vol_id, edge_id]
1281        q[1] = self.xmom[vol_id, edge_id]
1282        q[2] = self.ymom[vol_id, edge_id]
1283
1284        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
1285
1286
1287        r = rotate(q, normal, direction = 1)
1288        r[1] = -r[1]
1289        q = rotate(r, normal, direction = -1)
1290
1291        return q
1292
1293
1294
1295class Transmissive_Momentum_Set_Stage_boundary(Boundary):
1296    """Returns same momentum conserved quantities as
1297    those present in its neighbour volume.
1298    Sets stage by specifying a function f of time which may either be a
1299    vector function or a scalar function
1300
1301    Example:
1302
1303    def waveform(t):
1304        return sea_level + normalized_amplitude/cosh(t-25)**2
1305
1306    Bts = Transmissive_Momentum_Set_Stage_boundary(domain, waveform)
1307   
1308
1309    Underlying domain must be specified when boundary is instantiated
1310    """
1311
1312    def __init__(self, domain = None, function=None):
1313        Boundary.__init__(self)
1314
1315        if domain is None:
1316            msg = 'Domain must be specified for this type boundary'
1317            raise msg
1318
1319        if function is None:
1320            msg = 'Function must be specified for this type boundary'
1321            raise msg
1322
1323        self.domain   = domain
1324        self.function = function
1325
1326    def __repr__(self):
1327        return 'Transmissive_Momentum_Set_Stage_boundary(%s)' %self.domain
1328
1329    def evaluate(self, vol_id, edge_id):
1330        """Transmissive Momentum Set Stage boundaries return the edge momentum
1331        values of the volume they serve.
1332        """
1333
1334        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
1335        value = self.function(self.domain.time)
1336
1337        try:
1338            x = float(value)
1339        except:   
1340            x = float(value[0])
1341           
1342        q[0] = x
1343        return q
1344
1345
1346        #FIXME: Consider this (taken from File_boundary) to allow
1347        #spatial variation
1348        #if vol_id is not None and edge_id is not None:
1349        #    i = self.boundary_indices[ vol_id, edge_id ]
1350        #    return self.F(t, point_id = i)
1351        #else:
1352        #    return self.F(t)
1353
1354
1355
1356class Dirichlet_Discharge_boundary(Boundary):
1357    """
1358    Sets stage (stage0)
1359    Sets momentum (wh0) in the inward normal direction.
1360
1361    Underlying domain must be specified when boundary is instantiated
1362    """
1363
1364    def __init__(self, domain = None, stage0=None, wh0=None):
1365        Boundary.__init__(self)
1366
1367        if domain is None:
1368            msg = 'Domain must be specified for this type boundary'
1369            raise msg
1370
1371        if stage0 is None:
1372            raise 'set stage'
1373
1374        if wh0 is None:
1375            wh0 = 0.0
1376
1377        self.domain   = domain
1378        self.stage0  = stage0
1379        self.wh0 = wh0
1380
1381    def __repr__(self):
1382        return 'Dirichlet_Discharge_boundary(%s)' %self.domain
1383
1384    def evaluate(self, vol_id, edge_id):
1385        """Set discharge in the (inward) normal direction
1386        """
1387
1388        normal = self.domain.get_normal(vol_id,edge_id)
1389        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1390        return q
1391
1392
1393        #FIXME: Consider this (taken from File_boundary) to allow
1394        #spatial variation
1395        #if vol_id is not None and edge_id is not None:
1396        #    i = self.boundary_indices[ vol_id, edge_id ]
1397        #    return self.F(t, point_id = i)
1398        #else:
1399        #    return self.F(t)
1400
1401
1402class Field_boundary(Boundary):
1403    """Set boundary from given field represented in an sww file containing values
1404    for stage, xmomentum and ymomentum.
1405    Optionally, the user can specify mean_stage to offset the stage provided in the
1406    sww file.
1407
1408    This function is a thin wrapper around the generic File_boundary.
1409    """
1410
1411
1412    def __init__(self, filename, domain,
1413                 mean_stage=0.0,
1414                 time_thinning=1, 
1415                 use_cache=False,
1416                 verbose=False):
1417        """Constructor
1418
1419        filename: Name of sww file
1420        domain: pointer to shallow water domain for which the boundary applies
1421        mean_stage: The mean water level which will be added to stage derived from the sww file
1422        time_thinning:
1423        use_cache:
1424        verbose:
1425       
1426        """
1427
1428        # Create generic file_boundary object
1429        self.file_boundary = File_boundary(filename, domain,
1430                                           time_thinning=time_thinning,
1431                                           use_cache=use_cache,
1432                                           verbose=verbose)
1433       
1434        # Record information from File_boundary
1435        self.F = self.file_boundary.F
1436        self.domain = self.file_boundary.domain
1437       
1438        # Record mean stage
1439        self.mean_stage = mean_stage
1440
1441
1442    def __repr__(self):
1443        return 'Field boundary'
1444
1445
1446    def evaluate(self, vol_id=None, edge_id=None):
1447        """Return linearly interpolated values based on domain.time
1448
1449        vol_id and edge_id are ignored
1450        """
1451
1452        # Evaluate file boundary
1453        q = self.file_boundary.evaluate(vol_id, edge_id)
1454
1455        # Adjust stage
1456        for j, name in enumerate(self.domain.conserved_quantities):
1457            if name == 'stage':
1458                q[j] += self.mean_stage
1459        return q
1460
1461   
1462
1463#########################
1464#Standard forcing terms:
1465#
1466def gravity(domain):
1467    """Apply gravitational pull in the presence of bed slope
1468    """
1469
1470    xmom = domain.quantities['xmomentum'].explicit_update
1471    ymom = domain.quantities['ymomentum'].explicit_update
1472
1473    Stage = domain.quantities['stage']
1474    Elevation = domain.quantities['elevation']
1475    h = Stage.edge_values - Elevation.edge_values
1476    v = Elevation.vertex_values
1477
1478    x = domain.get_vertex_coordinates()
1479    g = domain.g
1480
1481    for k in range(len(domain)):
1482        avg_h = sum( h[k,:] )/3
1483
1484        #Compute bed slope
1485        x0, y0, x1, y1, x2, y2 = x[k,:]
1486        z0, z1, z2 = v[k,:]
1487
1488        zx, zy = gradient(x0, y0, x1, y1, x2, y2, z0, z1, z2)
1489
1490        #Update momentum
1491        xmom[k] += -g*zx*avg_h
1492        ymom[k] += -g*zy*avg_h
1493
1494
1495def gravity_c(domain):
1496    """Wrapper calling C version
1497    """
1498
1499    xmom = domain.quantities['xmomentum'].explicit_update
1500    ymom = domain.quantities['ymomentum'].explicit_update
1501
1502    Stage = domain.quantities['stage']
1503    Elevation = domain.quantities['elevation']
1504    h = Stage.edge_values - Elevation.edge_values
1505    v = Elevation.vertex_values
1506
1507    x = domain.get_vertex_coordinates()
1508    g = domain.g
1509
1510
1511    from shallow_water_ext import gravity
1512    gravity(g, h, v, x, xmom, ymom)
1513
1514
1515
1516def manning_friction(domain):
1517    """Apply (Manning) friction to water momentum
1518    (Python version)
1519    """
1520
1521    from math import sqrt
1522
1523    w = domain.quantities['stage'].centroid_values
1524    z = domain.quantities['elevation'].centroid_values
1525    h = w-z
1526
1527    uh = domain.quantities['xmomentum'].centroid_values
1528    vh = domain.quantities['ymomentum'].centroid_values
1529    eta = domain.quantities['friction'].centroid_values
1530
1531    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1532    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1533
1534    N = len(domain)
1535    eps = domain.minimum_allowed_height
1536    g = domain.g
1537
1538    for k in range(N):
1539        if eta[k] >= eps:
1540            if h[k] >= eps:
1541                S = -g * eta[k]**2 * sqrt((uh[k]**2 + vh[k]**2))
1542                S /= h[k]**(7.0/3)
1543
1544                #Update momentum
1545                xmom_update[k] += S*uh[k]
1546                ymom_update[k] += S*vh[k]
1547
1548
1549def manning_friction_implicit_c(domain):
1550    """Wrapper for c version
1551    """
1552
1553
1554    #print 'Implicit friction'
1555
1556    xmom = domain.quantities['xmomentum']
1557    ymom = domain.quantities['ymomentum']
1558
1559    w = domain.quantities['stage'].centroid_values
1560    z = domain.quantities['elevation'].centroid_values
1561
1562    uh = xmom.centroid_values
1563    vh = ymom.centroid_values
1564    eta = domain.quantities['friction'].centroid_values
1565
1566    xmom_update = xmom.semi_implicit_update
1567    ymom_update = ymom.semi_implicit_update
1568
1569    N = len(domain)
1570    eps = domain.minimum_allowed_height
1571    g = domain.g
1572
1573    from shallow_water_ext import manning_friction
1574    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1575
1576
1577def manning_friction_explicit_c(domain):
1578    """Wrapper for c version
1579    """
1580
1581    #print 'Explicit friction'
1582
1583    xmom = domain.quantities['xmomentum']
1584    ymom = domain.quantities['ymomentum']
1585
1586    w = domain.quantities['stage'].centroid_values
1587    z = domain.quantities['elevation'].centroid_values
1588
1589    uh = xmom.centroid_values
1590    vh = ymom.centroid_values
1591    eta = domain.quantities['friction'].centroid_values
1592
1593    xmom_update = xmom.explicit_update
1594    ymom_update = ymom.explicit_update
1595
1596    N = len(domain)
1597    eps = domain.minimum_allowed_height
1598    g = domain.g
1599
1600    from shallow_water_ext import manning_friction
1601    manning_friction(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1602
1603
1604def linear_friction(domain):
1605    """Apply linear friction to water momentum
1606
1607    Assumes quantity: 'linear_friction' to be present
1608    """
1609
1610    from math import sqrt
1611
1612    w = domain.quantities['stage'].centroid_values
1613    z = domain.quantities['elevation'].centroid_values
1614    h = w-z
1615
1616    uh = domain.quantities['xmomentum'].centroid_values
1617    vh = domain.quantities['ymomentum'].centroid_values
1618    tau = domain.quantities['linear_friction'].centroid_values
1619
1620    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1621    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1622
1623    N = len(domain) # number_of_triangles
1624    eps = domain.minimum_allowed_height
1625    g = domain.g #Not necessary? Why was this added?
1626
1627    for k in range(N):
1628        if tau[k] >= eps:
1629            if h[k] >= eps:
1630                S = -tau[k]/h[k]
1631
1632                #Update momentum
1633                xmom_update[k] += S*uh[k]
1634                ymom_update[k] += S*vh[k]
1635
1636
1637
1638def check_forcefield(f):
1639    """Check that f is either
1640    1: a callable object f(t,x,y), where x and y are vectors
1641       and that it returns an array or a list of same length
1642       as x and y
1643    2: a scalar
1644    """
1645
1646    if callable(f):
1647        N = 3
1648        x = ones(3, Float)
1649        y = ones(3, Float)
1650        try:
1651            q = f(1.0, x=x, y=y)
1652        except Exception, e:
1653            msg = 'Function %s could not be executed:\n%s' %(f, e)
1654            #FIXME: Reconsider this semantics
1655            raise msg
1656
1657        try:
1658            q = array(q).astype(Float)
1659        except:
1660            msg = 'Return value from vector function %s could ' %f
1661            msg += 'not be converted into a Numeric array of floats.\n'
1662            msg += 'Specified function should return either list or array.'
1663            raise msg
1664
1665        #Is this really what we want?
1666        msg = 'Return vector from function %s ' %f
1667        msg += 'must have same lenght as input vectors'
1668        assert len(q) == N, msg
1669
1670    else:
1671        try:
1672            f = float(f)
1673        except:
1674            msg = 'Force field %s must be either a scalar' %f
1675            msg += ' or a vector function'
1676            raise Exception(msg)
1677    return f
1678
1679
1680class Wind_stress:
1681    """Apply wind stress to water momentum in terms of
1682    wind speed [m/s] and wind direction [degrees]
1683    """
1684
1685    def __init__(self, *args, **kwargs):
1686        """Initialise windfield from wind speed s [m/s]
1687        and wind direction phi [degrees]
1688
1689        Inputs v and phi can be either scalars or Python functions, e.g.
1690
1691        W = Wind_stress(10, 178)
1692
1693        #FIXME - 'normal' degrees are assumed for now, i.e. the
1694        vector (1,0) has zero degrees.
1695        We may need to convert from 'compass' degrees later on and also
1696        map from True north to grid north.
1697
1698        Arguments can also be Python functions of t,x,y as in
1699
1700        def speed(t,x,y):
1701            ...
1702            return s
1703
1704        def angle(t,x,y):
1705            ...
1706            return phi
1707
1708        where x and y are vectors.
1709
1710        and then pass the functions in
1711
1712        W = Wind_stress(speed, angle)
1713
1714        The instantiated object W can be appended to the list of
1715        forcing_terms as in
1716
1717        Alternatively, one vector valued function for (speed, angle)
1718        can be applied, providing both quantities simultaneously.
1719        As in
1720        W = Wind_stress(F), where returns (speed, angle) for each t.
1721
1722        domain.forcing_terms.append(W)
1723        """
1724
1725        from anuga.config import rho_a, rho_w, eta_w
1726        from Numeric import array, Float
1727
1728        if len(args) == 2:
1729            s = args[0]
1730            phi = args[1]
1731        elif len(args) == 1:
1732            #Assume vector function returning (s, phi)(t,x,y)
1733            vector_function = args[0]
1734            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1735            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1736        else:
1737           #Assume info is in 2 keyword arguments
1738
1739           if len(kwargs) == 2:
1740               s = kwargs['s']
1741               phi = kwargs['phi']
1742           else:
1743               raise 'Assumes two keyword arguments: s=..., phi=....'
1744
1745        self.speed = check_forcefield(s)
1746        self.phi = check_forcefield(phi)
1747
1748        self.const = eta_w*rho_a/rho_w
1749
1750
1751    def __call__(self, domain):
1752        """Evaluate windfield based on values found in domain
1753        """
1754
1755        from math import pi, cos, sin, sqrt
1756        from Numeric import Float, ones, ArrayType
1757
1758        xmom_update = domain.quantities['xmomentum'].explicit_update
1759        ymom_update = domain.quantities['ymomentum'].explicit_update
1760
1761        N = len(domain) # number_of_triangles
1762        t = domain.time
1763
1764        if callable(self.speed):
1765            xc = domain.get_centroid_coordinates()
1766            s_vec = self.speed(t, xc[:,0], xc[:,1])
1767        else:
1768            #Assume s is a scalar
1769
1770            try:
1771                s_vec = self.speed * ones(N, Float)
1772            except:
1773                msg = 'Speed must be either callable or a scalar: %s' %self.s
1774                raise msg
1775
1776
1777        if callable(self.phi):
1778            xc = domain.get_centroid_coordinates()
1779            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1780        else:
1781            #Assume phi is a scalar
1782
1783            try:
1784                phi_vec = self.phi * ones(N, Float)
1785            except:
1786                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1787                raise msg
1788
1789        assign_windfield_values(xmom_update, ymom_update,
1790                                s_vec, phi_vec, self.const)
1791
1792
1793def assign_windfield_values(xmom_update, ymom_update,
1794                            s_vec, phi_vec, const):
1795    """Python version of assigning wind field to update vectors.
1796    A c version also exists (for speed)
1797    """
1798    from math import pi, cos, sin, sqrt
1799
1800    N = len(s_vec)
1801    for k in range(N):
1802        s = s_vec[k]
1803        phi = phi_vec[k]
1804
1805        #Convert to radians
1806        phi = phi*pi/180
1807
1808        #Compute velocity vector (u, v)
1809        u = s*cos(phi)
1810        v = s*sin(phi)
1811
1812        #Compute wind stress
1813        S = const * sqrt(u**2 + v**2)
1814        xmom_update[k] += S*u
1815        ymom_update[k] += S*v
1816
1817
1818
1819class Rainfall:
1820    """Class Rainfall - general 'rain over entire domain' forcing term.
1821   
1822    Used for implementing Rainfall over the entire domain.
1823       
1824        Current Limited to only One Gauge..
1825       
1826        Need to add Spatial Varying Capability
1827        (This module came from copying and amending the Inflow Code)
1828   
1829    Rainfall(rain)
1830       
1831    rain [mm/s]:  Total rain rate over the specified domain. 
1832                  NOTE: Raingauge Data needs to reflect the time step.
1833                  IE: if Gauge is mm read at a time step, then the input
1834                  here is as mm/(timeStep) so 10mm in 5minutes becomes
1835                  10/(5x60) = 0.0333mm/s.
1836       
1837       
1838                  This parameter can be either a constant or a
1839                  function of time. Positive values indicate inflow,
1840                  negative values indicate outflow.
1841                  (and be used for Infiltration - Write Seperate Module)
1842                  The specified flow will be divided by the area of
1843                  the inflow region and then applied to update the
1844                  quantity in question.
1845   
1846    Examples
1847    How to put them in a run File...
1848       
1849    #--------------------------------------------------------------------------
1850    # Setup specialised forcing terms
1851    #--------------------------------------------------------------------------
1852    # This is the new element implemented by Ole and Rudy to allow direct
1853    # input of Inflow in mm/s
1854
1855    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms')) 
1856                        # Note need path to File in String.
1857                        # Else assumed in same directory
1858
1859    domain.forcing_terms.append(catchmentrainfall)
1860    """
1861
1862    # FIXME (OLE): Add a polygon as an alternative.
1863    # FIXME (AnyOne) : Add various methods to allow spatial variations
1864    # FIXME (OLE): Generalise to all quantities
1865
1866    def __init__(self,
1867                 rain=0.0,
1868                 quantity_name='stage'):
1869
1870        self.rain = rain
1871        self.quantity_name = quantity_name
1872   
1873    def __call__(self, domain):
1874
1875        # Update rainfall
1876        if callable(self.rain):
1877            rain = self.rain(domain.get_time())
1878        else:
1879            rain = self.rain
1880
1881        # Now rain is a number
1882        quantity = domain.quantities[self.quantity_name].explicit_update
1883        quantity[:] += rain/1000  # Converting mm/s to m/s to apply in ANUGA
1884                # 1mm of rainfall is equivalent to 1 litre /m2
1885                # Flow is expressed as m3/s converted to a stage height in (m)
1886               
1887                # Note 1m3 = 1x10^9mm3 (mls)
1888                # or is that m3 to Litres ??? Check this how is it applied !!!
1889
1890
1891class Inflow:
1892    """Class Inflow - general 'rain and drain' forcing term.
1893   
1894    Useful for implementing flows in and out of the domain.
1895   
1896    Inflow(center, radius, flow)
1897   
1898    center [m]: Coordinates at center of flow point
1899    radius [m]: Size of circular area
1900    flow [m^3/s]: Total flow rate over the specified area. 
1901                  This parameter can be either a constant or a
1902                  function of time. Positive values indicate inflow,
1903                  negative values indicate outflow.
1904                  The specified flow will be divided by the area of
1905                  the inflow region and then applied to update the
1906                  quantity in question.
1907   
1908    Examples
1909
1910    # Constant drain at 0.003 m^3/s.
1911    # The outflow area is 0.07**2*pi=0.0154 m^2
1912    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
1913    #                                     
1914    Inflow((0.7, 0.4), 0.07, -0.003)
1915
1916
1917    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
1918    # The inflow area is 0.03**2*pi = 0.00283 m^2
1919    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s     
1920    # over the specified area
1921    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
1922
1923    #--------------------------------------------------------------------------
1924    # Setup specialised forcing terms
1925    #--------------------------------------------------------------------------
1926    # This is the new element implemented by Ole to allow direct input
1927    # of Inflow in m^3/s
1928
1929    hydrograph = Inflow(center=(320, 300), radius=10,
1930                        flow=file_function('Q/QPMF_Rot_Sub13.tms'))
1931
1932    domain.forcing_terms.append(hydrograph)
1933   
1934    """
1935
1936    # FIXME (OLE): Add a polygon as an alternative.
1937    # FIXME (OLE): Generalise to all quantities
1938
1939    def __init__(self,
1940                 center=None, radius=None,
1941                 flow=0.0,
1942                 quantity_name = 'stage'):
1943
1944        from math import pi
1945
1946       
1947                 
1948        if center is not None and radius is not None:
1949            assert len(center) == 2
1950        else:
1951            msg = 'Both center and radius must be specified'
1952            raise Exception, msg
1953   
1954        self.center = center
1955        self.radius = radius
1956        self.area = radius**2*pi
1957        self.flow = flow
1958        self.quantity_name = quantity_name
1959   
1960    def __call__(self, domain):
1961
1962        # Determine indices in flow area
1963        if not hasattr(self, 'indices'):
1964            center = self.center
1965            radius = self.radius
1966           
1967            N = len(domain)   
1968            self.indices = []
1969            coordinates = domain.get_centroid_coordinates()     
1970            for k in range(N):
1971                x, y = coordinates[k,:] # Centroid
1972                if ((x-center[0])**2+(y-center[1])**2) < radius**2:
1973                    self.indices.append(k)   
1974
1975        # Update inflow
1976        if callable(self.flow):
1977            flow = self.flow(domain.get_time())
1978        else:
1979            flow = self.flow
1980
1981        # Now flow is a number
1982       
1983        quantity = domain.quantities[self.quantity_name].explicit_update
1984        for k in self.indices:
1985            quantity[k] += flow/self.area                       
1986
1987
1988##############################
1989#OBSOLETE STUFF
1990
1991def balance_deep_and_shallow_old(domain):
1992    """Compute linear combination between stage as computed by
1993    gradient-limiters and stage computed as constant height above bed.
1994    The former takes precedence when heights are large compared to the
1995    bed slope while the latter takes precedence when heights are
1996    relatively small.  Anything in between is computed as a balanced
1997    linear combination in order to avoid numerical disturbances which
1998    would otherwise appear as a result of hard switching between
1999    modes.
2000    """
2001
2002    #OBSOLETE
2003
2004    #Shortcuts
2005    wc = domain.quantities['stage'].centroid_values
2006    zc = domain.quantities['elevation'].centroid_values
2007    hc = wc - zc
2008
2009    wv = domain.quantities['stage'].vertex_values
2010    zv = domain.quantities['elevation'].vertex_values
2011    hv = wv-zv
2012
2013
2014    #Computed linear combination between constant stages and and
2015    #stages parallel to the bed elevation.
2016    for k in range(len(domain)):
2017        #Compute maximal variation in bed elevation
2018        #  This quantitiy is
2019        #    dz = max_i abs(z_i - z_c)
2020        #  and it is independent of dimension
2021        #  In the 1d case zc = (z0+z1)/2
2022        #  In the 2d case zc = (z0+z1+z2)/3
2023
2024        dz = max(abs(zv[k,0]-zc[k]),
2025                 abs(zv[k,1]-zc[k]),
2026                 abs(zv[k,2]-zc[k]))
2027
2028
2029        hmin = min( hv[k,:] )
2030
2031        #Create alpha in [0,1], where alpha==0 means using shallow
2032        #first order scheme and alpha==1 means using the stage w as
2033        #computed by the gradient limiter (1st or 2nd order)
2034        #
2035        #If hmin > dz/2 then alpha = 1 and the bed will have no effect
2036        #If hmin < 0 then alpha = 0 reverting to constant height above bed.
2037
2038        if dz > 0.0:
2039            alpha = max( min( 2*hmin/dz, 1.0), 0.0 )
2040        else:
2041            #Flat bed
2042            alpha = 1.0
2043
2044        #Weighted balance between stage parallel to bed elevation
2045        #(wvi = zvi + hc) and stage as computed by 1st or 2nd
2046        #order gradient limiter
2047        #(wvi = zvi + hvi) where i=0,1,2 denotes the vertex ids
2048        #
2049        #It follows that the updated wvi is
2050        #  wvi := (1-alpha)*(zvi+hc) + alpha*(zvi+hvi) =
2051        #  zvi + hc + alpha*(hvi - hc)
2052        #
2053        #Note that hvi = zc+hc-zvi in the first order case (constant).
2054
2055        if alpha < 1:
2056            for i in range(3):
2057                wv[k,i] = zv[k,i] + hc[k] + alpha*(hv[k,i]-hc[k])
2058
2059
2060            #Momentums at centroids
2061            xmomc = domain.quantities['xmomentum'].centroid_values
2062            ymomc = domain.quantities['ymomentum'].centroid_values
2063
2064            #Momentums at vertices
2065            xmomv = domain.quantities['xmomentum'].vertex_values
2066            ymomv = domain.quantities['ymomentum'].vertex_values
2067
2068            # Update momentum as a linear combination of
2069            # xmomc and ymomc (shallow) and momentum
2070            # from extrapolator xmomv and ymomv (deep).
2071            xmomv[k,:] = (1-alpha)*xmomc[k] + alpha*xmomv[k,:]
2072            ymomv[k,:] = (1-alpha)*ymomc[k] + alpha*ymomv[k,:]
2073
2074
2075
2076
2077
2078###########################
2079###########################
2080#Geometries
2081
2082
2083#FIXME: Rethink this way of creating values.
2084
2085
2086class Weir:
2087    """Set a bathymetry for weir with a hole and a downstream gutter
2088    x,y are assumed to be in the unit square
2089    """
2090
2091    def __init__(self, stage):
2092        self.inflow_stage = stage
2093
2094    def __call__(self, x, y):
2095        from Numeric import zeros, Float
2096        from math import sqrt
2097
2098        N = len(x)
2099        assert N == len(y)
2100
2101        z = zeros(N, Float)
2102        for i in range(N):
2103            z[i] = -x[i]/2  #General slope
2104
2105            #Flattish bit to the left
2106            if x[i] < 0.3:
2107                z[i] = -x[i]/10
2108
2109            #Weir
2110            if x[i] >= 0.3 and x[i] < 0.4:
2111                z[i] = -x[i]+0.9
2112
2113            #Dip
2114            x0 = 0.6
2115            #depth = -1.3
2116            depth = -1.0
2117            #plateaux = -0.9
2118            plateaux = -0.6
2119            if y[i] < 0.7:
2120                if x[i] > x0 and x[i] < 0.9:
2121                    z[i] = depth
2122
2123                #RHS plateaux
2124                if x[i] >= 0.9:
2125                    z[i] = plateaux
2126
2127
2128            elif y[i] >= 0.7 and y[i] < 1.5:
2129                #Restrict and deepen
2130                if x[i] >= x0 and x[i] < 0.8:
2131                    z[i] = depth-(y[i]/3-0.3)
2132                    #z[i] = depth-y[i]/5
2133                    #z[i] = depth
2134                elif x[i] >= 0.8:
2135                    #RHS plateaux
2136                    z[i] = plateaux
2137
2138            elif y[i] >= 1.5:
2139                if x[i] >= x0 and x[i] < 0.8 + (y[i]-1.5)/1.2:
2140                    #Widen up and stay at constant depth
2141                    z[i] = depth-1.5/5
2142                elif x[i] >= 0.8 + (y[i]-1.5)/1.2:
2143                    #RHS plateaux
2144                    z[i] = plateaux
2145
2146
2147            #Hole in weir (slightly higher than inflow condition)
2148            if x[i] >= 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
2149                z[i] = -x[i]+self.inflow_stage + 0.02
2150
2151            #Channel behind weir
2152            x0 = 0.5
2153            if x[i] >= 0.4 and x[i] < x0 and y[i] > 0.2 and y[i] < 0.4:
2154                z[i] = -x[i]+self.inflow_stage + 0.02
2155
2156            if x[i] >= x0 and x[i] < 0.6 and y[i] > 0.2 and y[i] < 0.4:
2157                #Flatten it out towards the end
2158                z[i] = -x0+self.inflow_stage + 0.02 + (x0-x[i])/5
2159
2160            #Hole to the east
2161            x0 = 1.1; y0 = 0.35
2162            #if x[i] < -0.2 and y < 0.5:
2163            if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
2164                z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-1.0
2165
2166            #Tiny channel draining hole
2167            if x[i] >= 1.14 and x[i] < 1.2 and y[i] >= 0.4 and y[i] < 0.6:
2168                z[i] = -0.9 #North south
2169
2170            if x[i] >= 0.9 and x[i] < 1.18 and y[i] >= 0.58 and y[i] < 0.65:
2171                z[i] = -1.0 + (x[i]-0.9)/3 #East west
2172
2173
2174
2175            #Stuff not in use
2176
2177            #Upward slope at inlet to the north west
2178            #if x[i] < 0.0: # and y[i] > 0.5:
2179            #    #z[i] = -y[i]+0.5  #-x[i]/2
2180            #    z[i] = x[i]/4 - y[i]**2 + 0.5
2181
2182            #Hole to the west
2183            #x0 = -0.4; y0 = 0.35 # center
2184            #if sqrt((2*(x[i]-x0))**2 + (2*(y[i]-y0))**2) < 0.2:
2185            #    z[i] = sqrt(((x[i]-x0))**2 + ((y[i]-y0))**2)-0.2
2186
2187
2188
2189
2190
2191        return z/2
2192
2193class Weir_simple:
2194    """Set a bathymetry for weir with a hole and a downstream gutter
2195    x,y are assumed to be in the unit square
2196    """
2197
2198    def __init__(self, stage):
2199        self.inflow_stage = stage
2200
2201    def __call__(self, x, y):
2202        from Numeric import zeros, Float
2203
2204        N = len(x)
2205        assert N == len(y)
2206
2207        z = zeros(N, Float)
2208        for i in range(N):
2209            z[i] = -x[i]  #General slope
2210
2211            #Flat bit to the left
2212            if x[i] < 0.3:
2213                z[i] = -x[i]/10  #General slope
2214
2215            #Weir
2216            if x[i] > 0.3 and x[i] < 0.4:
2217                z[i] = -x[i]+0.9
2218
2219            #Dip
2220            if x[i] > 0.6 and x[i] < 0.9:
2221                z[i] = -x[i]-0.5  #-y[i]/5
2222
2223            #Hole in weir (slightly higher than inflow condition)
2224            if x[i] > 0.3 and x[i] < 0.4 and y[i] > 0.2 and y[i] < 0.4:
2225                z[i] = -x[i]+self.inflow_stage + 0.05
2226
2227
2228        return z/2
2229
2230
2231
2232class Constant_stage:
2233    """Set an initial condition with constant stage
2234    """
2235    def __init__(self, s):
2236        self.s = s
2237
2238    def __call__(self, x, y):
2239        return self.s
2240
2241class Constant_height:
2242    """Set an initial condition with constant water height, e.g
2243    stage s = z+h
2244    """
2245
2246    def __init__(self, W, h):
2247        self.W = W
2248        self.h = h
2249
2250    def __call__(self, x, y):
2251        if self.W is None:
2252            from Numeric import ones, Float
2253            return self.h*ones(len(x), Float)
2254        else:
2255            return self.W(x,y) + self.h
2256
2257
2258
2259
2260def compute_fluxes_python(domain):
2261    """Compute all fluxes and the timestep suitable for all volumes
2262    in domain.
2263
2264    Compute total flux for each conserved quantity using "flux_function"
2265
2266    Fluxes across each edge are scaled by edgelengths and summed up
2267    Resulting flux is then scaled by area and stored in
2268    explicit_update for each of the three conserved quantities
2269    stage, xmomentum and ymomentum
2270
2271    The maximal allowable speed computed by the flux_function for each volume
2272    is converted to a timestep that must not be exceeded. The minimum of
2273    those is computed as the next overall timestep.
2274
2275    Post conditions:
2276      domain.explicit_update is reset to computed flux values
2277      domain.timestep is set to the largest step satisfying all volumes.
2278    """
2279
2280    import sys
2281    from Numeric import zeros, Float
2282
2283    N = len(domain) # number_of_triangles
2284
2285    #Shortcuts
2286    Stage = domain.quantities['stage']
2287    Xmom = domain.quantities['xmomentum']
2288    Ymom = domain.quantities['ymomentum']
2289    Bed = domain.quantities['elevation']
2290
2291    #Arrays
2292    stage = Stage.edge_values
2293    xmom =  Xmom.edge_values
2294    ymom =  Ymom.edge_values
2295    bed =   Bed.edge_values
2296
2297    stage_bdry = Stage.boundary_values
2298    xmom_bdry =  Xmom.boundary_values
2299    ymom_bdry =  Ymom.boundary_values
2300
2301    flux = zeros((N,3), Float) #Work array for summing up fluxes
2302
2303    #Loop
2304    timestep = float(sys.maxint)
2305    for k in range(N):
2306
2307        for i in range(3):
2308            #Quantities inside volume facing neighbour i
2309            ql = [stage[k, i], xmom[k, i], ymom[k, i]]
2310            zl = bed[k, i]
2311
2312            #Quantities at neighbour on nearest face
2313            n = domain.neighbours[k,i]
2314            if n < 0:
2315                m = -n-1 #Convert negative flag to index
2316                qr = [stage_bdry[m], xmom_bdry[m], ymom_bdry[m]]
2317                zr = zl #Extend bed elevation to boundary
2318            else:
2319                m = domain.neighbour_edges[k,i]
2320                qr = [stage[n, m], xmom[n, m], ymom[n, m]]
2321                zr = bed[n, m]
2322
2323
2324            #Outward pointing normal vector
2325            normal = domain.normals[k, 2*i:2*i+2]
2326
2327            #Flux computation using provided function
2328            edgeflux, max_speed = flux_function(normal, ql, qr, zl, zr)
2329
2330            flux[k,:] = edgeflux
2331
2332    return flux
2333
2334
2335
2336
2337
2338
2339
2340##############################################
2341#Initialise module
2342
2343
2344from anuga.utilities import compile
2345if compile.can_use_C_extension('shallow_water_ext.c'):
2346    #Replace python version with c implementations
2347
2348    from shallow_water_ext import rotate, assign_windfield_values
2349    compute_fluxes = compute_fluxes_c
2350    extrapolate_second_order_sw=extrapolate_second_order_sw_c
2351    gravity = gravity_c
2352    manning_friction = manning_friction_implicit_c
2353    h_limiter = h_limiter_c
2354    balance_deep_and_shallow = balance_deep_and_shallow_c
2355    protect_against_infinitesimal_and_negative_heights =\
2356                    protect_against_infinitesimal_and_negative_heights_c
2357
2358    #distribute_to_vertices_and_edges =\
2359    #              distribute_to_vertices_and_edges_c #(like MH's)
2360
2361
2362
2363#Optimisation with psyco
2364from anuga.config import use_psyco
2365if use_psyco:
2366    try:
2367        import psyco
2368    except:
2369        import os
2370        if os.name == 'posix' and os.uname()[4] == 'x86_64':
2371            pass
2372            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2373        else:
2374            msg = 'WARNING: psyco (speedup) could not import'+\
2375                  ', you may want to consider installing it'
2376            print msg
2377    else:
2378        psyco.bind(Domain.distribute_to_vertices_and_edges)
2379        psyco.bind(Domain.compute_fluxes)
2380
2381if __name__ == "__main__":
2382    pass
2383
2384# Profiling stuff
2385#import profile
2386#profiler = profile.Profile()
Note: See TracBrowser for help on using the repository browser.