source: trunk/anuga_core/source/anuga/shallow_water/shallow_water_domain.py @ 8068

Last change on this file since 8068 was 8068, checked in by habili, 13 years ago

Fixed bug where starttime and duration in the evolve function did not produce correct results. Starttime is now set in the constructor of Domain and is 0 by default. time is is to starttime. Also a bug was fixed where duration did not correctly calculate the finaltime. Associated unit tests have also been fixed to reflect the change.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 47.5 KB
Line 
1"""
2Finite-volume computations of the shallow water wave equation.
3
4Title: ANGUA shallow_water_domain - 2D triangular domains for finite-volume
5       computations of the shallow water wave equation.
6
7
8Author: Ole Nielsen, Ole.Nielsen@ga.gov.au
9        Stephen Roberts, Stephen.Roberts@anu.edu.au
10        Duncan Gray, Duncan.Gray@ga.gov.au
11
12CreationDate: 2004
13
14Description:
15    This module contains a specialisation of class Domain from
16    module domain.py consisting of methods specific to the
17    Shallow Water Wave Equation
18
19    U_t + E_x + G_y = S
20
21    where
22
23    U = [w, uh, vh]
24    E = [uh, u^2h + gh^2/2, uvh]
25    G = [vh, uvh, v^2h + gh^2/2]
26    S represents source terms forcing the system
27    (e.g. gravity, friction, wind stress, ...)
28
29    and _t, _x, _y denote the derivative with respect to t, x and y
30    respectively.
31
32
33    The quantities are
34
35    symbol    variable name    explanation
36    x         x                horizontal distance from origin [m]
37    y         y                vertical distance from origin [m]
38    z         elevation        elevation of bed on which flow is modelled [m]
39    h         height           water height above z [m]
40    w         stage            absolute water level, w = z+h [m]
41    u                          speed in the x direction [m/s]
42    v                          speed in the y direction [m/s]
43    uh        xmomentum        momentum in the x direction [m^2/s]
44    vh        ymomentum        momentum in the y direction [m^2/s]
45
46    eta                        mannings friction coefficient [to appear]
47    nu                         wind stress coefficient [to appear]
48
49    The conserved quantities are w, uh, vh
50
51Reference:
52    Catastrophic Collapse of Water Supply Reservoirs in Urban Areas,
53    Christopher Zoppou and Stephen Roberts,
54    Journal of Hydraulic Engineering, vol. 127, No. 7 July 1999
55
56    Hydrodynamic modelling of coastal inundation.
57    Nielsen, O., S. Roberts, D. Gray, A. McPherson and A. Hitchman
58    In Zerger, A. and Argent, R.M. (eds) MODSIM 2005 International Congress on
59    Modelling and Simulation. Modelling and Simulation Society of Australia and
60    New Zealand, December 2005, pp. 518-523. ISBN: 0-9758400-2-9.
61    http://www.mssanz.org.au/modsim05/papers/nielsen.pdf
62
63
64SeeAlso:
65    TRAC administration of ANUGA (User Manuals etc) at
66    https://datamining.anu.edu.au/anuga and Subversion repository at
67    $HeadURL: https://datamining.anu.edu.au/svn/anuga/trunk/anuga_core/source/
68                anuga/shallow_water/shallow_water_domain.py $
69
70Constraints: See GPL license in the user guide
71Version: 1.0 ($Revision: 8068 $)
72ModifiedBy:
73    $Author: habili $
74    $Date: 2010-11-11 00:55:52 +0000 (Thu, 11 Nov 2010) $
75"""
76
77
78import numpy as num
79
80from anuga.abstract_2d_finite_volumes.generic_domain \
81                    import Generic_Domain
82
83from anuga.shallow_water.forcing import Cross_section
84from anuga.utilities.numerical_tools import mean
85from anuga.file.sww import SWW_file 
86           
87import anuga.utilities.log as log
88
89import types
90
91class Domain(Generic_Domain):
92    """ Class for a shallow water domain."""
93    def __init__(self,
94                 coordinates=None,
95                 vertices=None,
96                 boundary=None,
97                 tagged_elements=None,
98                 geo_reference=None,
99                 use_inscribed_circle=False,
100                 mesh_filename=None,
101                 use_cache=False,
102                 verbose=False,
103                 conserved_quantities = None,
104                 evolved_quantities = None,
105                 other_quantities = None,
106                 full_send_dict=None,
107                 ghost_recv_dict=None,
108                 starttime=0,
109                 processor=0,
110                 numproc=1,
111                 number_of_full_nodes=None,
112                 number_of_full_triangles=None):
113        """
114            Instantiate a shallow water domain.
115            coordinates - vertex locations for the mesh
116            vertices - vertex indices for the mesh
117            boundary - boundaries of the mesh
118            # @param tagged_elements
119            # @param geo_reference
120            # @param use_inscribed_circle
121            # @param mesh_filename
122            # @param use_cache
123            # @param verbose
124            # @param evolved_quantities
125            # @param full_send_dict
126            # @param ghost_recv_dict
127            # @param processor
128            # @param numproc
129            # @param number_of_full_nodes
130            # @param number_of_full_triangles
131        """
132
133        # Define quantities for the shallow_water domain
134        if conserved_quantities == None:
135            conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
136
137        if evolved_quantities == None:
138            evolved_quantities =  ['stage', 'xmomentum', 'ymomentum']
139           
140        if other_quantities == None:
141            other_quantities = ['elevation', 'friction']
142       
143       
144        Generic_Domain.__init__(self,
145                            coordinates,
146                            vertices,
147                            boundary,
148                            conserved_quantities,
149                            evolved_quantities,
150                            other_quantities,
151                            tagged_elements,
152                            geo_reference,
153                            use_inscribed_circle,
154                            mesh_filename,
155                            use_cache,
156                            verbose,
157                            full_send_dict,
158                            ghost_recv_dict,
159                            starttime,
160                            processor,
161                            numproc,
162                            number_of_full_nodes=number_of_full_nodes,
163                            number_of_full_triangles=number_of_full_triangles)
164
165        self.set_defaults()
166
167 
168        self.forcing_terms.append(manning_friction_implicit)
169        self.forcing_terms.append(gravity)
170
171
172        self.fractional_step_operators = []
173
174        # Stored output
175        self.store = True
176        self.set_store_vertices_uniquely(False)
177        self.quantities_to_be_stored = {'elevation': 1, 
178                                        'stage': 2, 
179                                        'xmomentum': 2, 
180                                        'ymomentum': 2}
181
182
183    def set_defaults(self):
184        """Set the default values in this routine. That way we can inherit class
185        and just over redefine the defaults for the new class
186        """
187
188        from anuga.config import minimum_storable_height
189        from anuga.config import minimum_allowed_height, maximum_allowed_speed
190        from anuga.config import g, beta_w, beta_w_dry, \
191             beta_uh, beta_uh_dry, beta_vh, beta_vh_dry, tight_slope_limiters
192        from anuga.config import alpha_balance
193        from anuga.config import optimise_dry_cells
194        from anuga.config import optimised_gradient_limiter
195        from anuga.config import use_edge_limiter
196        from anuga.config import use_centroid_velocities
197
198        self.set_minimum_allowed_height(minimum_allowed_height)
199        self.maximum_allowed_speed = maximum_allowed_speed
200
201        self.g = g
202        self.beta_w = beta_w
203        self.beta_w_dry = beta_w_dry
204        self.beta_uh = beta_uh
205        self.beta_uh_dry = beta_uh_dry
206        self.beta_vh = beta_vh
207        self.beta_vh_dry = beta_vh_dry
208        self.alpha_balance = alpha_balance
209
210        self.tight_slope_limiters = tight_slope_limiters
211        self.optimise_dry_cells = optimise_dry_cells
212
213       
214        self.set_sloped_mannings_function(False)
215
216        self.minimum_storable_height = minimum_storable_height
217
218         # Limiters
219        self.use_edge_limiter = use_edge_limiter
220        self.optimised_gradient_limiter = optimised_gradient_limiter
221        self.use_centroid_velocities = use_centroid_velocities       
222
223
224    def set_sloped_mannings_function(self, flag=True):
225        """Set mannings friction function to use the sloped
226        wetted area.
227        The flag is tested in the python wrapper
228        mannings_friction_implicit
229        """
230        if flag:
231            self.use_sloped_mannings = True
232        else:
233            self.use_sloped_mannings = False
234
235
236    def set_use_edge_limiter(self, flag=True):
237        """Cludge to allow unit test to pass, but to
238        also introduce new edge limiting. The flag is
239        tested in distribute_to_vertices_and_edges
240        """
241        if flag:
242            self.use_edge_limiter = True
243        else:
244            self.use_edge_limiter = False
245
246
247    def set_beta(self, beta):
248        """Shorthand to assign one constant value [0,2] to all limiters.
249        0 Corresponds to first order, where as larger values make use of
250        the second order scheme.
251        """
252
253        self.beta_w = beta
254        self.beta_w_dry = beta
255        self.quantities['stage'].beta = beta
256
257        self.beta_uh = beta
258        self.beta_uh_dry = beta
259        self.quantities['xmomentum'].beta = beta
260
261        self.beta_vh = beta
262        self.beta_vh_dry = beta
263        self.quantities['ymomentum'].beta = beta
264
265
266    def set_store_vertices_uniquely(self, flag, reduction=None):
267        """Decide whether vertex values should be stored uniquely as
268        computed in the model (True) or whether they should be reduced to one
269        value per vertex using self.reduction (False).
270        """
271
272        # FIXME (Ole): how about using the word "continuous vertex values" or
273        # "continuous stage surface"
274        self.smooth = not flag
275
276        # Reduction operation for get_vertex_values
277        if reduction is None:
278            self.reduction = mean
279            #self.reduction = min  #Looks better near steep slopes
280
281    ##
282    # @brief Set the minimum depth that will be written to an SWW file.
283    # @param minimum_storable_height The minimum stored height (in m).
284    def set_minimum_storable_height(self, minimum_storable_height):
285        """Set the minimum depth that will be recognised when writing
286        to an sww file. This is useful for removing thin water layers
287        that seems to be caused by friction creep.
288
289        The minimum allowed sww depth is in meters.
290        """
291
292        self.minimum_storable_height = minimum_storable_height
293
294    ##
295    # @brief
296    # @param minimum_allowed_height
297    def set_minimum_allowed_height(self, minimum_allowed_height):
298        """Set minimum depth that will be recognised in the numerical scheme.
299
300        The minimum allowed depth is in meters.
301
302        The parameter H0 (Minimal height for flux computation)
303        is also set by this function
304        """
305
306        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
307
308        #FIXME (Ole): Maybe use histogram to identify isolated extreme speeds
309        #and deal with them adaptively similarly to how we used to use 1 order
310        #steps to recover.
311
312        self.minimum_allowed_height = minimum_allowed_height
313        self.H0 = minimum_allowed_height
314
315    ##
316    # @brief
317    # @param maximum_allowed_speed
318    def set_maximum_allowed_speed(self, maximum_allowed_speed):
319        """Set the maximum particle speed that is allowed in water
320        shallower than minimum_allowed_height. This is useful for
321        controlling speeds in very thin layers of water and at the same time
322        allow some movement avoiding pooling of water.
323        """
324
325        self.maximum_allowed_speed = maximum_allowed_speed
326
327    ##
328    # @brief
329    # @param points_file_block_line_size
330    def set_points_file_block_line_size(self, points_file_block_line_size):
331        """Set the minimum depth that will be recognised when writing
332        to an sww file. This is useful for removing thin water layers
333        that seems to be caused by friction creep.
334
335        The minimum allowed sww depth is in meters.
336        """
337        self.points_file_block_line_size = points_file_block_line_size
338
339       
340    # FIXME: Probably obsolete in its curren form   
341    ##
342    # @brief Set the quantities that will be written to an SWW file.
343    # @param q The quantities to be written.
344    # @note Param 'q' may be None, single quantity or list of quantity strings.
345    # @note If 'q' is None, no quantities will be stored in the SWW file.
346    def set_quantities_to_be_stored(self, q):
347        """Specify which quantities will be stored in the sww file
348       
349        q must be either:
350          - a dictionary with quantity names
351          - a list of quantity names (for backwards compatibility)
352          - None
353
354        The format of the dictionary is as follows
355       
356        quantity_name: flag where flag must be either 1 or 2.
357        If flag is 1, the quantity is considered static and will be
358        stored once at the beginning of the simulation in a 1D array.
359       
360        If flag is 2, the quantity is considered time dependent and
361        it will be stored at each yieldstep by appending it to the
362        appropriate 2D array in the sww file.   
363       
364        If q is None, storage will be switched off altogether.
365       
366        Once the simulation has started and thw sww file opened,
367        this function will have no effect.
368       
369        The format, where q is a list of names is for backwards compatibility
370        only.
371        It will take the specified quantities to be time dependent and assume
372        'elevation' to be static regardless.
373        """
374
375        if q is None:
376            self.quantities_to_be_stored = {}
377            self.store = False
378            return
379
380        # Check correctness
381        for quantity_name in q:
382            msg = ('Quantity %s is not a valid conserved quantity'
383                   % quantity_name)
384            assert quantity_name in self.quantities, msg
385
386        assert type(q) == types.DictType   
387        self.quantities_to_be_stored = q
388
389    ##
390    # @brief
391    # @param indices
392    def get_wet_elements(self, indices=None):
393        """Return indices for elements where h > minimum_allowed_height
394
395        Optional argument:
396            indices is the set of element ids that the operation applies to.
397
398        Usage:
399            indices = get_wet_elements()
400
401        Note, centroid values are used for this operation
402        """
403
404        # Water depth below which it is considered to be 0 in the model
405        # FIXME (Ole): Allow this to be specified as a keyword argument as well
406        from anuga.config import minimum_allowed_height
407
408        elevation = self.get_quantity('elevation').\
409                        get_values(location='centroids', indices=indices)
410        stage = self.get_quantity('stage').\
411                    get_values(location='centroids', indices=indices)
412        depth = stage - elevation
413
414        # Select indices for which depth > 0
415        wet_indices = num.compress(depth > minimum_allowed_height,
416                                   num.arange(len(depth)))
417        return wet_indices
418
419    ##
420    # @brief
421    # @param indices
422    def get_maximum_inundation_elevation(self, indices=None):
423        """Return highest elevation where h > 0
424
425        Optional argument:
426            indices is the set of element ids that the operation applies to.
427
428        Usage:
429            q = get_maximum_inundation_elevation()
430
431        Note, centroid values are used for this operation
432        """
433
434        wet_elements = self.get_wet_elements(indices)
435        return self.get_quantity('elevation').\
436                   get_maximum_value(indices=wet_elements)
437
438    ##
439    # @brief
440    # @param indices
441    def get_maximum_inundation_location(self, indices=None):
442        """Return location of highest elevation where h > 0
443
444        Optional argument:
445            indices is the set of element ids that the operation applies to.
446
447        Usage:
448            q = get_maximum_inundation_location()
449
450        Note, centroid values are used for this operation
451        """
452
453        wet_elements = self.get_wet_elements(indices)
454        return self.get_quantity('elevation').\
455                   get_maximum_location(indices=wet_elements)
456
457
458    def get_flow_through_cross_section(self, polyline, verbose=False):
459        """Get the total flow through an arbitrary poly line.
460
461        This is a run-time equivalent of the function with same name
462        in sww_interrogate.py
463
464        Input:
465            polyline: Representation of desired cross section - it may contain
466                      multiple sections allowing for complex shapes. Assume
467                      absolute UTM coordinates.
468                      Format [[x0, y0], [x1, y1], ...]
469
470        Output:
471            Q: Total flow [m^3/s] across given segments.
472        """
473
474
475        cross_section = Cross_section(self, polyline, verbose)
476
477        return cross_section.get_flow_through_cross_section()
478
479
480    def get_energy_through_cross_section(self, polyline,
481                                         kind='total',
482                                         verbose=False):
483        """Obtain average energy head [m] across specified cross section.
484
485        Inputs:
486            polyline: Representation of desired cross section - it may contain
487                      multiple sections allowing for complex shapes. Assume
488                      absolute UTM coordinates.
489                      Format [[x0, y0], [x1, y1], ...]
490            kind:     Select which energy to compute.
491                      Options are 'specific' and 'total' (default)
492
493        Output:
494            E: Average energy [m] across given segments for all stored times.
495
496        The average velocity is computed for each triangle intersected by
497        the polyline and averaged weighted by segment lengths.
498
499        The typical usage of this function would be to get average energy of
500        flow in a channel, and the polyline would then be a cross section
501        perpendicular to the flow.
502
503        #FIXME (Ole) - need name for this energy reflecting that its dimension
504        is [m].
505        """
506
507
508
509        cross_section = Cross_section(self, polyline, verbose)
510
511        return cross_section.get_energy_through_cross_section(kind)
512
513
514    def check_integrity(self):
515        """ Run integrity checks on shallow water domain. """
516        Generic_Domain.check_integrity(self)
517
518        #Check that we are solving the shallow water wave equation
519        msg = 'First conserved quantity must be "stage"'
520        assert self.conserved_quantities[0] == 'stage', msg
521        msg = 'Second conserved quantity must be "xmomentum"'
522        assert self.conserved_quantities[1] == 'xmomentum', msg
523        msg = 'Third conserved quantity must be "ymomentum"'
524        assert self.conserved_quantities[2] == 'ymomentum', msg
525
526    def extrapolate_second_order_sw(self):
527        """Call correct module function
528            (either from this module or C-extension)"""
529        extrapolate_second_order_sw(self)
530
531    def compute_fluxes(self):
532        """Call correct module function
533            (either from this module or C-extension)"""
534        compute_fluxes(self)
535
536
537    def distribute_to_vertices_and_edges(self):
538        """ Call correct module function """
539        if self.use_edge_limiter:
540            distribute_using_edge_limiter(self)
541        else:
542            distribute_using_vertex_limiter(self)
543
544
545
546    def evolve(self,
547               yieldstep=None,
548               finaltime=None,
549               duration=None,
550               skip_initial_step=False):
551        """Specialisation of basic evolve method from parent class.
552       
553            Evolve the model by 1 step.
554        """
555
556        # Call check integrity here rather than from user scripts
557        # self.check_integrity()
558
559        msg = 'Attribute self.beta_w must be in the interval [0, 2]'
560        assert 0 <= self.beta_w <= 2.0, msg
561
562        # Initial update of vertex and edge values before any STORAGE
563        # and or visualisation.
564        # This is done again in the initialisation of the Generic_Domain
565        # evolve loop but we do it here to ensure the values are ok for storage.
566        self.distribute_to_vertices_and_edges()
567
568        if self.store is True and self.get_time() == self.get_starttime():
569            self.initialise_storage()
570
571        # Call basic machinery from parent class
572        for t in Generic_Domain.evolve(self, yieldstep=yieldstep,
573                                       finaltime=finaltime, duration=duration,
574                                       skip_initial_step=skip_initial_step):
575            # Store model data, e.g. for subsequent visualisation
576            if self.store is True:
577                self.store_timestep()
578
579            # Pass control on to outer loop for more specific actions
580            yield(t)
581
582
583    def initialise_storage(self):
584        """Create and initialise self.writer object for storing data.
585        Also, save x,y and bed elevation
586        """
587       
588        # Initialise writer
589        self.writer = SWW_file(self)
590
591        # Store vertices and connectivity
592        self.writer.store_connectivity()
593
594
595    def store_timestep(self):
596        """Store time dependent quantities and time.
597
598        Precondition:
599           self.writer has been initialised
600        """
601
602        self.writer.store_timestep()
603
604
605    def timestepping_statistics(self,
606                                track_speeds=False,
607                                triangle_id=None):
608        """Return string with time stepping statistics for printing or logging
609
610        Optional boolean keyword track_speeds decides whether to report
611        location of smallest timestep as well as a histogram and percentile
612        report.
613        """
614
615        from anuga.config import epsilon, g
616
617        # Call basic machinery from parent class
618        msg = Generic_Domain.timestepping_statistics(self, track_speeds,
619                                                     triangle_id)
620
621        if track_speeds is True:
622            # qwidth determines the text field used for quantities
623            qwidth = self.qwidth
624
625            # Selected triangle
626            k = self.k
627
628            # Report some derived quantities at vertices, edges and centroid
629            # specific to the shallow water wave equation
630            z = self.quantities['elevation']
631            w = self.quantities['stage']
632
633            Vw = w.get_values(location='vertices', indices=[k])[0]
634            Ew = w.get_values(location='edges', indices=[k])[0]
635            Cw = w.get_values(location='centroids', indices=[k])
636
637            Vz = z.get_values(location='vertices', indices=[k])[0]
638            Ez = z.get_values(location='edges', indices=[k])[0]
639            Cz = z.get_values(location='centroids', indices=[k])
640
641            name = 'depth'
642            Vh = Vw-Vz
643            Eh = Ew-Ez
644            Ch = Cw-Cz
645
646            message  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
647                 % (name.ljust(qwidth), Vh[0], Vh[1], Vh[2])
648
649            message += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
650                 % (name.ljust(qwidth), Eh[0], Eh[1], Eh[2])
651
652            message += '    %s: centroid_value = %.4f\n'\
653                 % (name.ljust(qwidth), Ch[0])
654
655            msg += message
656
657            uh = self.quantities['xmomentum']
658            vh = self.quantities['ymomentum']
659
660            Vuh = uh.get_values(location='vertices', indices=[k])[0]
661            Euh = uh.get_values(location='edges', indices=[k])[0]
662            Cuh = uh.get_values(location='centroids', indices=[k])
663
664            Vvh = vh.get_values(location='vertices', indices=[k])[0]
665            Evh = vh.get_values(location='edges', indices=[k])[0]
666            Cvh = vh.get_values(location='centroids', indices=[k])
667
668            # Speeds in each direction
669            Vu = Vuh/(Vh + epsilon)
670            Eu = Euh/(Eh + epsilon)
671            Cu = Cuh/(Ch + epsilon)
672            name = 'U'
673            message  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n' \
674                 % (name.ljust(qwidth), Vu[0], Vu[1], Vu[2])
675
676            message += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n' \
677                 % (name.ljust(qwidth), Eu[0], Eu[1], Eu[2])
678
679            message += '    %s: centroid_value = %.4f\n' \
680                 % (name.ljust(qwidth), Cu[0])
681
682            msg += message
683
684            Vv = Vvh/(Vh + epsilon)
685            Ev = Evh/(Eh + epsilon)
686            Cv = Cvh/(Ch + epsilon)
687            name = 'V'
688            message  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n' \
689                 % (name.ljust(qwidth), Vv[0], Vv[1], Vv[2])
690
691            message += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n' \
692                 % (name.ljust(qwidth), Ev[0], Ev[1], Ev[2])
693
694            message += '    %s: centroid_value = %.4f\n'\
695                 %(name.ljust(qwidth), Cv[0])
696
697            msg += message
698
699            # Froude number in each direction
700            name = 'Froude (x)'
701            Vfx = Vu/(num.sqrt(g*Vh) + epsilon)
702            Efx = Eu/(num.sqrt(g*Eh) + epsilon)
703            Cfx = Cu/(num.sqrt(g*Ch) + epsilon)
704
705            message  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
706                 % (name.ljust(qwidth), Vfx[0], Vfx[1], Vfx[2])
707
708            message += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
709                 % (name.ljust(qwidth), Efx[0], Efx[1], Efx[2])
710
711            message += '    %s: centroid_value = %.4f\n'\
712                 % (name.ljust(qwidth), Cfx[0])
713
714            msg += message
715
716            name = 'Froude (y)'
717            Vfy = Vv/(num.sqrt(g*Vh) + epsilon)
718            Efy = Ev/(num.sqrt(g*Eh) + epsilon)
719            Cfy = Cv/(num.sqrt(g*Ch) + epsilon)
720
721            message  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
722                 % (name.ljust(qwidth), Vfy[0], Vfy[1], Vfy[2])
723
724            message += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
725                 % (name.ljust(qwidth), Efy[0], Efy[1], Efy[2])
726
727            message += '    %s: centroid_value = %.4f\n'\
728                 % (name.ljust(qwidth), Cfy[0])
729
730            msg += message
731
732        return msg
733       
734       
735
736    def compute_boundary_flows(self):
737        """Compute boundary flows at current timestep.
738                       
739        Quantities computed are:
740           Total inflow across boundary
741           Total outflow across boundary
742           Flow across each tagged boundary segment
743        """
744               
745        # Run through boundary array and compute for each segment
746        # the normal momentum ((uh, vh) dot normal) times segment length.
747        # Based on sign accumulate this into boundary_inflow and
748        # boundary_outflow.
749                       
750        # Compute flows along boundary
751       
752        uh = self.get_quantity('xmomentum').get_values(location='edges')
753        vh = self.get_quantity('ymomentum').get_values(location='edges')       
754       
755        # Loop through edges that lie on the boundary and calculate
756        # flows
757        boundary_flows = {}
758        total_boundary_inflow = 0.0
759        total_boundary_outflow = 0.0
760        for vol_id, edge_id in self.boundary:
761            # Compute normal flow across edge. Since normal vector points
762            # away from triangle, a positive sign means that water
763            # flows *out* from this triangle.
764             
765            momentum = [uh[vol_id, edge_id], vh[vol_id, edge_id]]
766            normal = self.mesh.get_normal(vol_id, edge_id)
767            length = self.mesh.get_edgelength(vol_id, edge_id)           
768            normal_flow = num.dot(momentum, normal)*length
769           
770            # Reverse sign so that + is taken to mean inflow
771            # and - means outflow. This is more intuitive.
772            edge_flow = -normal_flow
773           
774            # Tally up inflows and outflows separately
775            if edge_flow > 0:
776                # Flow is inflow     
777                total_boundary_inflow += edge_flow       
778            else:
779                # Flow is outflow
780                total_boundary_outflow += edge_flow   
781
782            # Tally up flows by boundary tag
783            tag = self.boundary[(vol_id, edge_id)]
784           
785            if tag not in boundary_flows:
786                boundary_flows[tag] = 0.0
787            boundary_flows[tag] += edge_flow
788           
789               
790        return boundary_flows, total_boundary_inflow, total_boundary_outflow
791       
792
793    def compute_forcing_flows(self):
794        """Compute flows in and out of domain due to forcing terms.
795                       
796        Quantities computed are:
797               
798       
799           Total inflow through forcing terms
800           Total outflow through forcing terms
801           Current total volume in domain       
802
803        """
804
805        #FIXME(Ole): We need to separate what part of explicit_update was
806        # due to the normal flux calculations and what is due to forcing terms.
807       
808        pass
809                       
810       
811    def compute_total_volume(self):
812        """Compute total volume (m^3) of water in entire domain
813        """
814       
815        area = self.mesh.get_areas()
816       
817        stage = self.get_quantity('stage').get_values(location='centroids')
818        elevation = \
819            self.get_quantity('elevation').get_values(location='centroids')
820        depth = stage-elevation
821       
822        return num.sum(depth*area)
823       
824       
825    def volumetric_balance_statistics(self):               
826        """Create volumetric balance report suitable for printing or logging.
827        """
828       
829        (boundary_flows, total_boundary_inflow,
830         total_boundary_outflow) = self.compute_boundary_flows() 
831       
832        message = '---------------------------\n'       
833        message += 'Volumetric balance report:\n'
834        message += '--------------------------\n'
835        message += 'Total boundary inflow [m^3/s]: %.2f\n' % total_boundary_inflow
836        message += 'Total boundary outflow [m^3/s]: %.2f\n' % total_boundary_outflow
837        message += 'Net boundary flow by tags [m^3/s]\n'
838        for tag in boundary_flows:
839            message += '    %s [m^3/s]: %.2f\n' % (tag, boundary_flows[tag])
840       
841        message += 'Total net boundary flow [m^3/s]: %.2f\n' % \
842                    (total_boundary_inflow + total_boundary_outflow) 
843        message += 'Total volume in domain [m^3]: %.2f\n' % \
844                    self.compute_total_volume()
845       
846        # The go through explicit forcing update and record the rate of change
847        # for stage and
848        # record into forcing_inflow and forcing_outflow. Finally compute 
849        # integral of depth to obtain total volume of domain.
850       
851        # FIXME(Ole): This part is not yet done.               
852       
853        return message       
854           
855################################################################################
856# End of class Shallow Water Domain
857################################################################################
858
859#-----------------
860# Flux computation
861#-----------------
862
863## @brief Compute fluxes and timestep suitable for all volumes in domain.
864# @param domain The domain to calculate fluxes for.
865def compute_fluxes(domain):
866    """Compute fluxes and timestep suitable for all volumes in domain.
867
868    Compute total flux for each conserved quantity using "flux_function"
869
870    Fluxes across each edge are scaled by edgelengths and summed up
871    Resulting flux is then scaled by area and stored in
872    explicit_update for each of the three conserved quantities
873    stage, xmomentum and ymomentum
874
875    The maximal allowable speed computed by the flux_function for each volume
876    is converted to a timestep that must not be exceeded. The minimum of
877    those is computed as the next overall timestep.
878
879    Post conditions:
880      domain.explicit_update is reset to computed flux values
881      domain.timestep is set to the largest step satisfying all volumes.
882
883    This wrapper calls the underlying C version of compute fluxes
884    """
885
886    import sys
887    from shallow_water_ext import compute_fluxes_ext_central \
888                                  as compute_fluxes_ext
889
890    # Shortcuts
891    Stage = domain.quantities['stage']
892    Xmom = domain.quantities['xmomentum']
893    Ymom = domain.quantities['ymomentum']
894    Bed = domain.quantities['elevation']
895
896    timestep = float(sys.maxint)
897
898    flux_timestep = compute_fluxes_ext(timestep,
899                                       domain.epsilon,
900                                       domain.H0,
901                                       domain.g,
902                                       domain.neighbours,
903                                       domain.neighbour_edges,
904                                       domain.normals,
905                                       domain.edgelengths,
906                                       domain.radii,
907                                       domain.areas,
908                                       domain.tri_full_flag,
909                                       Stage.edge_values,
910                                       Xmom.edge_values,
911                                       Ymom.edge_values,
912                                       Bed.edge_values,
913                                       Stage.boundary_values,
914                                       Xmom.boundary_values,
915                                       Ymom.boundary_values,
916                                       Stage.explicit_update,
917                                       Xmom.explicit_update,
918                                       Ymom.explicit_update,
919                                       domain.already_computed_flux,
920                                       domain.max_speed,
921                                       int(domain.optimise_dry_cells))
922
923    domain.flux_timestep = flux_timestep
924
925################################################################################
926# Module functions for gradient limiting
927################################################################################
928
929##
930# @brief Wrapper for C version of extrapolate_second_order_sw.
931# @param domain The domain to operate on.
932# @note MH090605 The following method belongs to the shallow_water domain class
933#       see comments in the corresponding method in shallow_water_ext.c
934def extrapolate_second_order_sw(domain):
935    """Wrapper calling C version of extrapolate_second_order_sw"""
936
937    from shallow_water_ext import extrapolate_second_order_sw as extrapol2
938
939    # Shortcuts
940    Stage = domain.quantities['stage']
941    Xmom = domain.quantities['xmomentum']
942    Ymom = domain.quantities['ymomentum']
943    Elevation = domain.quantities['elevation']
944
945    extrapol2(domain,
946              domain.surrogate_neighbours,
947              domain.number_of_boundaries,
948              domain.centroid_coordinates,
949              Stage.centroid_values,
950              Xmom.centroid_values,
951              Ymom.centroid_values,
952              Elevation.centroid_values,
953              domain.vertex_coordinates,
954              Stage.vertex_values,
955              Xmom.vertex_values,
956              Ymom.vertex_values,
957              Elevation.vertex_values,
958              int(domain.optimise_dry_cells))
959
960##
961# @brief Distribution from centroids to vertices specific to the SWW eqn.
962# @param domain The domain to operate on.
963def distribute_using_vertex_limiter(domain):
964    """Distribution from centroids to vertices specific to the SWW equation.
965
966    It will ensure that h (w-z) is always non-negative even in the
967    presence of steep bed-slopes by taking a weighted average between shallow
968    and deep cases.
969
970    In addition, all conserved quantities get distributed as per either a
971    constant (order==1) or a piecewise linear function (order==2).
972
973    FIXME: more explanation about removal of artificial variability etc
974
975    Precondition:
976      All quantities defined at centroids and bed elevation defined at
977      vertices.
978
979    Postcondition
980      Conserved quantities defined at vertices
981    """
982
983    # Remove very thin layers of water
984    protect_against_infinitesimal_and_negative_heights(domain)
985
986    # Extrapolate all conserved quantities
987    if domain.optimised_gradient_limiter:
988        # MH090605 if second order,
989        # perform the extrapolation and limiting on
990        # all of the conserved quantities
991
992        if (domain._order_ == 1):
993            for name in domain.conserved_quantities:
994                Q = domain.quantities[name]
995                Q.extrapolate_first_order()
996        elif domain._order_ == 2:
997            domain.extrapolate_second_order_sw()
998        else:
999            raise Exception('Unknown order')
1000    else:
1001        # Old code:
1002        for name in domain.conserved_quantities:
1003            Q = domain.quantities[name]
1004
1005            if domain._order_ == 1:
1006                Q.extrapolate_first_order()
1007            elif domain._order_ == 2:
1008                Q.extrapolate_second_order_and_limit_by_vertex()
1009            else:
1010                raise Exception('Unknown order')
1011
1012    # Take bed elevation into account when water heights are small
1013    balance_deep_and_shallow(domain)
1014
1015    # Compute edge values by interpolation
1016    for name in domain.conserved_quantities:
1017        Q = domain.quantities[name]
1018        Q.interpolate_from_vertices_to_edges()
1019
1020##
1021# @brief Distribution from centroids to edges specific to the SWW eqn.
1022# @param domain The domain to operate on.
1023def distribute_using_edge_limiter(domain):
1024    """Distribution from centroids to edges specific to the SWW eqn.
1025
1026    It will ensure that h (w-z) is always non-negative even in the
1027    presence of steep bed-slopes by taking a weighted average between shallow
1028    and deep cases.
1029
1030    In addition, all conserved quantities get distributed as per either a
1031    constant (order==1) or a piecewise linear function (order==2).
1032
1033
1034    Precondition:
1035      All quantities defined at centroids and bed elevation defined at
1036      vertices.
1037
1038    Postcondition
1039      Conserved quantities defined at vertices
1040    """
1041
1042    # Remove very thin layers of water
1043    protect_against_infinitesimal_and_negative_heights(domain)
1044
1045    for name in domain.conserved_quantities:
1046        Q = domain.quantities[name]
1047        if domain._order_ == 1:
1048            Q.extrapolate_first_order()
1049        elif domain._order_ == 2:
1050            Q.extrapolate_second_order_and_limit_by_edge()
1051        else:
1052            raise Exception('Unknown order')
1053
1054    balance_deep_and_shallow(domain)
1055
1056    # Compute edge values by interpolation
1057    for name in domain.conserved_quantities:
1058        Q = domain.quantities[name]
1059        Q.interpolate_from_vertices_to_edges()
1060
1061##
1062# @brief  Protect against infinitesimal heights and associated high velocities.
1063# @param domain The domain to operate on.
1064def protect_against_infinitesimal_and_negative_heights(domain):
1065    """Protect against infinitesimal heights and associated high velocities"""
1066
1067    from shallow_water_ext import protect
1068
1069    # Shortcuts
1070    wc = domain.quantities['stage'].centroid_values
1071    zc = domain.quantities['elevation'].centroid_values
1072    xmomc = domain.quantities['xmomentum'].centroid_values
1073    ymomc = domain.quantities['ymomentum'].centroid_values
1074
1075    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
1076            domain.epsilon, wc, zc, xmomc, ymomc)
1077
1078##
1079# @brief Wrapper for C function balance_deep_and_shallow_c().
1080# @param domain The domain to operate on.
1081def balance_deep_and_shallow(domain):
1082    """Compute linear combination between stage as computed by
1083    gradient-limiters limiting using w, and stage computed by
1084    gradient-limiters limiting using h (h-limiter).
1085    The former takes precedence when heights are large compared to the
1086    bed slope while the latter takes precedence when heights are
1087    relatively small.  Anything in between is computed as a balanced
1088    linear combination in order to avoid numerical disturbances which
1089    would otherwise appear as a result of hard switching between
1090    modes.
1091
1092    Wrapper for C implementation
1093    """
1094
1095    from shallow_water_ext import balance_deep_and_shallow \
1096                                  as balance_deep_and_shallow_c
1097
1098    # Shortcuts
1099    wc = domain.quantities['stage'].centroid_values
1100    zc = domain.quantities['elevation'].centroid_values
1101    wv = domain.quantities['stage'].vertex_values
1102    zv = domain.quantities['elevation'].vertex_values
1103
1104    # Momentums at centroids
1105    xmomc = domain.quantities['xmomentum'].centroid_values
1106    ymomc = domain.quantities['ymomentum'].centroid_values
1107
1108    # Momentums at vertices
1109    xmomv = domain.quantities['xmomentum'].vertex_values
1110    ymomv = domain.quantities['ymomentum'].vertex_values
1111
1112    balance_deep_and_shallow_c(domain,
1113                               wc, zc, wv, zv, wc,
1114                               xmomc, ymomc, xmomv, ymomv)
1115
1116
1117
1118################################################################################
1119# Standard forcing terms
1120################################################################################
1121
1122##
1123# @brief Apply gravitational pull in the presence of bed slope.
1124# @param domain The domain to apply gravity to.
1125# @note Wrapper for C function gravity_c().
1126def gravity(domain):
1127    """Apply gravitational pull in the presence of bed slope
1128    Wrapper calls underlying C implementation
1129    """
1130
1131    from shallow_water_ext import gravity as gravity_c
1132
1133    xmom_update = domain.quantities['xmomentum'].explicit_update
1134    ymom_update = domain.quantities['ymomentum'].explicit_update
1135
1136    stage = domain.quantities['stage']
1137    elevation = domain.quantities['elevation']
1138
1139    #FIXME SR Should avoid allocating memory!
1140    height = stage.centroid_values - elevation.centroid_values
1141    elevation = elevation.vertex_values
1142
1143    point = domain.get_vertex_coordinates()
1144
1145    gravity_c(domain.g, height, elevation, point, xmom_update, ymom_update)
1146
1147##
1148# @brief Apply friction to a surface (implicit).
1149# @param domain The domain to apply Manning friction to.
1150# @note Wrapper for C function manning_friction_c().
1151def manning_friction_implicit(domain):
1152    """Apply (Manning) friction to water momentum
1153    Wrapper for c version
1154    """
1155
1156    from shallow_water_ext import manning_friction_flat
1157    from shallow_water_ext import manning_friction_sloped
1158
1159    xmom = domain.quantities['xmomentum']
1160    ymom = domain.quantities['ymomentum']
1161
1162    x = domain.get_vertex_coordinates()
1163   
1164    w = domain.quantities['stage'].centroid_values
1165    z = domain.quantities['elevation'].vertex_values
1166
1167    uh = xmom.centroid_values
1168    vh = ymom.centroid_values
1169    eta = domain.quantities['friction'].centroid_values
1170
1171    xmom_update = xmom.semi_implicit_update
1172    ymom_update = ymom.semi_implicit_update
1173
1174    eps = domain.minimum_allowed_height
1175    g = domain.g
1176
1177    if domain.use_sloped_mannings:
1178        manning_friction_sloped(g, eps, x, w, uh, vh, z, eta, xmom_update, \
1179                                ymom_update)
1180    else:
1181        manning_friction_flat(g, eps, w, uh, vh, z, eta, xmom_update, \
1182                                ymom_update)
1183   
1184
1185##
1186# @brief Apply friction to a surface (explicit).
1187# @param domain The domain to apply Manning friction to.
1188# @note Wrapper for C function manning_friction_c().
1189def manning_friction_explicit(domain):
1190    """Apply (Manning) friction to water momentum
1191    Wrapper for c version
1192    """
1193
1194    from shallow_water_ext import manning_friction_flat
1195    from shallow_water_ext import manning_friction_sloped
1196
1197    xmom = domain.quantities['xmomentum']
1198    ymom = domain.quantities['ymomentum']
1199
1200    x = domain.get_vertex_coordinates()
1201   
1202    w = domain.quantities['stage'].centroid_values
1203    z = domain.quantities['elevation'].vertex_values
1204
1205    uh = xmom.centroid_values
1206    vh = ymom.centroid_values
1207    eta = domain.quantities['friction'].centroid_values
1208
1209    xmom_update = xmom.explicit_update
1210    ymom_update = ymom.explicit_update
1211
1212    eps = domain.minimum_allowed_height
1213
1214    if domain.use_sloped_mannings:
1215        manning_friction_sloped(domain.g, eps, x, w, uh, vh, z, eta, xmom_update, \
1216                            ymom_update)
1217    else:
1218        manning_friction_flat(domain.g, eps, w, uh, vh, z, eta, xmom_update, \
1219                            ymom_update)
1220
1221
1222
1223# FIXME (Ole): This was implemented for use with one of the analytical solutions
1224##
1225# @brief Apply linear friction to a surface.
1226# @param domain The domain to apply Manning friction to.
1227# @note Is this still used (30 Oct 2007)?
1228def linear_friction(domain):
1229    """Apply linear friction to water momentum
1230
1231    Assumes quantity: 'linear_friction' to be present
1232    """
1233
1234    w = domain.quantities['stage'].centroid_values
1235    z = domain.quantities['elevation'].centroid_values
1236    h = w-z
1237
1238    uh = domain.quantities['xmomentum'].centroid_values
1239    vh = domain.quantities['ymomentum'].centroid_values
1240    tau = domain.quantities['linear_friction'].centroid_values
1241
1242    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1243    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1244
1245    num_tris = len(domain)
1246    eps = domain.minimum_allowed_height
1247
1248    for k in range(num_tris):
1249        if tau[k] >= eps:
1250            if h[k] >= eps:
1251                S = -tau[k]/h[k]
1252
1253                #Update momentum
1254                xmom_update[k] += S*uh[k]
1255                ymom_update[k] += S*vh[k]
1256
1257def depth_dependent_friction(domain, default_friction,
1258                             surface_roughness_data,
1259                             verbose=False):
1260    """Returns an array of friction values for each wet element adjusted for
1261            depth.
1262
1263    Inputs:
1264        domain - computational domain object
1265        default_friction - depth independent bottom friction
1266        surface_roughness_data - N x 5 array of n0, d1, n1, d2, n2 values
1267        for each friction region.
1268
1269    Outputs:
1270        wet_friction - Array that can be used directly to update friction as
1271                        follows:
1272                       domain.set_quantity('friction', wet_friction)
1273
1274       
1275       
1276    """
1277   
1278    default_n0 = 0  # James - this was missing, don't know what it should be
1279   
1280    # Create a temp array to store updated depth dependent
1281    # friction for wet elements
1282    # EHR this is outwardly inneficient but not obvious how to avoid
1283    # recreating each call??????
1284
1285    wet_friction    = num.zeros(len(domain), num.float)
1286    wet_friction[:] = default_n0  # Initially assign default_n0 to all array so
1287                                  # sure have no zeros values
1288   
1289    # create depth instance for this timestep   
1290    depth = domain.create_quantity_from_expression('stage - elevation') 
1291    # Recompute depth as vector 
1292    d_vals = depth.get_values(location='centroids')
1293 
1294    # rebuild the 'friction' values adjusted for depth at this instant
1295    # loop for each wet element in domain
1296   
1297    for i in domain.get_wet_elements():       
1298        # Get roughness data for each element
1299        d1 = float(surface_roughness_data[i, 1])
1300        n1 = float(surface_roughness_data[i, 2])
1301        d2 = float(surface_roughness_data[i, 3])
1302        n2 = float(surface_roughness_data[i, 4])
1303       
1304       
1305        # Recompute friction values from depth for this element
1306               
1307        if d_vals[i]   <= d1: 
1308            ddf = n1
1309        elif d_vals[i] >= d2:
1310            ddf = n2
1311        else:
1312            ddf = n1 + ((n2-n1)/(d2-d1))*(d_vals[i]-d1)
1313           
1314        # check sanity of result
1315        if (ddf  < 0.010 or \
1316                            ddf > 9999.0) :
1317            log.critical('>>>> WARNING: computed depth_dependent friction '
1318                         'out of range, ddf%f, n1=%f, n2=%f'
1319                         % (ddf, n1, n2))
1320       
1321        # update depth dependent friction  for that wet element
1322        wet_friction[i] = ddf
1323       
1324    # EHR add code to show range of 'friction across domain at this instant as
1325    # sanity check?????????
1326   
1327    if verbose :
1328        # return array of domain nvals
1329        nvals = domain.get_quantity('friction').get_values(location='centroids')
1330        n_min = min(nvals)
1331        n_max = max(nvals)
1332       
1333        log.critical('         ++++ calculate_depth_dependent_friction - '
1334                     'Updated friction - range  %7.3f to %7.3f'
1335                     % (n_min, n_max))
1336   
1337    return wet_friction
1338
1339
1340
1341################################################################################
1342# Initialise module
1343################################################################################
1344
1345def _raise_compile_exception():
1346    """ Raise exception if compiler not available. """
1347    msg = 'C implementations could not be accessed by %s.\n ' % __file__
1348    msg += 'Make sure compile_all.py has been run as described in '
1349    msg += 'the ANUGA installation guide.'
1350    raise Exception(msg)
1351
1352from anuga.utilities import compile
1353if not compile.can_use_C_extension('shallow_water_ext.c'):
1354    _raise_compile_exception()
1355
1356if __name__ == "__main__":
1357    pass
Note: See TracBrowser for help on using the repository browser.