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

Last change on this file since 6653 was 6653, checked in by ole, 15 years ago

Started work on volumetric check as per ticket:324 - then visitors arrived.
Will try to continue later.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 75.7 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
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: 6653 $)
70ModifiedBy:
71    $Author: ole $
72    $Date: 2009-03-28 02:26:15 +0000 (Sat, 28 Mar 2009) $
73
74"""
75
76# Subversion keywords:
77#
78# $LastChangedDate: 2009-03-28 02:26:15 +0000 (Sat, 28 Mar 2009) $
79# $LastChangedRevision: 6653 $
80# $LastChangedBy: ole $
81
82import Numeric as num
83
84from anuga.abstract_2d_finite_volumes.neighbour_mesh import segment_midpoints
85from anuga.abstract_2d_finite_volumes.domain import Domain as Generic_Domain
86from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
87     import Boundary
88from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
89     import File_boundary
90from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
91     import Dirichlet_boundary
92from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
93     import Time_boundary
94from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
95     import Transmissive_boundary
96from anuga.pmesh.mesh_interface import create_mesh_from_regions
97from anuga.utilities.numerical_tools import gradient, mean, ensure_numeric
98from anuga.geospatial_data.geospatial_data import ensure_geospatial
99
100from anuga.config import minimum_storable_height
101from anuga.config import minimum_allowed_height, maximum_allowed_speed
102from anuga.config import g, epsilon, beta_w, beta_w_dry,\
103     beta_uh, beta_uh_dry, beta_vh, beta_vh_dry, tight_slope_limiters
104from anuga.config import alpha_balance
105from anuga.config import optimise_dry_cells
106from anuga.config import optimised_gradient_limiter
107from anuga.config import use_edge_limiter
108from anuga.config import use_centroid_velocities
109from anuga.config import netcdf_mode_r, netcdf_mode_w, netcdf_mode_a
110
111from anuga.fit_interpolate.interpolate import Modeltime_too_late, Modeltime_too_early
112
113from anuga.utilities.polygon import inside_polygon, polygon_area, is_inside_polygon
114
115from types import IntType, FloatType
116from warnings import warn
117
118
119
120#
121# Shallow water domain
122#---------------------
123class Domain(Generic_Domain):
124
125    conserved_quantities = ['stage', 'xmomentum', 'ymomentum']
126    other_quantities = ['elevation', 'friction']
127   
128    def __init__(self,
129                 coordinates=None,
130                 vertices=None,
131                 boundary=None,
132                 tagged_elements=None,
133                 geo_reference=None,
134                 use_inscribed_circle=False,
135                 mesh_filename=None,
136                 use_cache=False,
137                 verbose=False,
138                 full_send_dict=None,
139                 ghost_recv_dict=None,
140                 processor=0,
141                 numproc=1,
142                 number_of_full_nodes=None,
143                 number_of_full_triangles=None):
144
145
146        other_quantities = ['elevation', 'friction']
147        Generic_Domain.__init__(self,
148                                coordinates,
149                                vertices,
150                                boundary,
151                                Domain.conserved_quantities,
152                                Domain.other_quantities,
153                                tagged_elements,
154                                geo_reference,
155                                use_inscribed_circle,
156                                mesh_filename,
157                                use_cache,
158                                verbose,
159                                full_send_dict,
160                                ghost_recv_dict,
161                                processor,
162                                numproc,
163                                number_of_full_nodes=number_of_full_nodes,
164                                number_of_full_triangles=number_of_full_triangles) 
165
166
167        self.set_minimum_allowed_height(minimum_allowed_height)
168       
169        self.maximum_allowed_speed = maximum_allowed_speed
170        self.g = g
171        self.beta_w      = beta_w
172        self.beta_w_dry  = beta_w_dry
173        self.beta_uh     = beta_uh
174        self.beta_uh_dry = beta_uh_dry
175        self.beta_vh     = beta_vh
176        self.beta_vh_dry = beta_vh_dry
177        self.alpha_balance = alpha_balance
178
179        self.tight_slope_limiters = tight_slope_limiters
180        self.optimise_dry_cells = optimise_dry_cells
181
182        self.forcing_terms.append(manning_friction_implicit)
183        self.forcing_terms.append(gravity)
184
185        # Stored output
186        self.store = True
187        self.format = 'sww'
188        self.set_store_vertices_uniquely(False)
189        self.minimum_storable_height = minimum_storable_height
190        self.quantities_to_be_stored = ['stage','xmomentum','ymomentum']
191
192        # Limiters
193        self.use_edge_limiter = use_edge_limiter
194        self.optimised_gradient_limiter = optimised_gradient_limiter
195        self.use_centroid_velocities = use_centroid_velocities
196
197
198    def set_all_limiters(self, beta):
199        """Shorthand to assign one constant value [0,1[ to all limiters.
200        0 Corresponds to first order, where as larger values make use of
201        the second order scheme.
202        """
203
204        self.beta_w      = beta
205        self.beta_w_dry  = beta
206        self.quantities['stage'].beta = beta
207       
208        self.beta_uh     = beta
209        self.beta_uh_dry = beta
210        self.quantities['xmomentum'].beta = beta
211       
212        self.beta_vh     = beta
213        self.beta_vh_dry = beta
214        self.quantities['ymomentum'].beta = beta
215       
216       
217
218    def set_store_vertices_uniquely(self, flag, reduction=None):
219        """Decide whether vertex values should be stored uniquely as
220        computed in the model or whether they should be reduced to one
221        value per vertex using self.reduction.
222        """
223
224        # FIXME (Ole): how about using the word continuous vertex values?
225        self.smooth = not flag
226
227        # Reduction operation for get_vertex_values
228        if reduction is None:
229            self.reduction = mean
230            #self.reduction = min  #Looks better near steep slopes
231
232
233    def set_minimum_storable_height(self, minimum_storable_height):
234        """
235        Set the minimum depth that will be recognised when writing
236        to an sww file. This is useful for removing thin water layers
237        that seems to be caused by friction creep.
238
239        The minimum allowed sww depth is in meters.
240        """
241        self.minimum_storable_height = minimum_storable_height
242
243
244    def set_minimum_allowed_height(self, minimum_allowed_height):
245        """
246        Set the minimum depth that will be recognised in the numerical
247        scheme
248
249        The minimum allowed depth is in meters.
250
251        The parameter H0 (Minimal height for flux computation)
252        is also set by this function
253        """
254
255        #FIXME (Ole): rename H0 to minimum_allowed_height_in_flux_computation
256
257        #FIXME (Ole): Maybe use histogram to identify isolated extreme speeds
258        #and deal with them adaptively similarly to how we used to use 1 order
259        #steps to recover.
260        self.minimum_allowed_height = minimum_allowed_height
261        self.H0 = minimum_allowed_height   
262       
263
264    def set_maximum_allowed_speed(self, maximum_allowed_speed):
265        """
266        Set the maximum particle speed that is allowed in water
267        shallower than minimum_allowed_height. This is useful for
268        controlling speeds in very thin layers of water and at the same time
269        allow some movement avoiding pooling of water.
270
271        """
272        self.maximum_allowed_speed = maximum_allowed_speed
273
274
275    def set_points_file_block_line_size(self,points_file_block_line_size):
276        """
277        Set the minimum depth that will be recognised when writing
278        to an sww file. This is useful for removing thin water layers
279        that seems to be caused by friction creep.
280
281        The minimum allowed sww depth is in meters.
282        """
283        self.points_file_block_line_size = points_file_block_line_size
284       
285       
286    def set_quantities_to_be_stored(self, q):
287        """Specify which quantities will be stored in the sww file.
288
289        q must be either:
290          - the name of a quantity
291          - a list of quantity names
292          - None
293
294        In the two first cases, the named quantities will be stored at
295        each yieldstep (This is in addition to the quantities elevation
296        and friction)
297       
298        If q is None, storage will be switched off altogether.
299        """
300
301
302        if q is None:
303            self.quantities_to_be_stored = []
304            self.store = False
305            return
306
307        if isinstance(q, basestring):
308            q = [q] # Turn argument into a list
309
310        # Check correcness
311        for quantity_name in q:
312            msg = 'Quantity %s is not a valid conserved quantity'\
313                  %quantity_name
314           
315            assert quantity_name in self.conserved_quantities, msg
316
317        self.quantities_to_be_stored = q
318
319
320
321    def get_wet_elements(self, indices=None):
322        """Return indices for elements where h > minimum_allowed_height
323
324        Optional argument:
325            indices is the set of element ids that the operation applies to.
326
327        Usage:
328            indices = get_wet_elements()
329
330        Note, centroid values are used for this operation           
331        """
332
333        # Water depth below which it is considered to be 0 in the model
334        # FIXME (Ole): Allow this to be specified as a keyword argument as well
335        from anuga.config import minimum_allowed_height
336
337       
338        elevation = self.get_quantity('elevation').\
339                    get_values(location='centroids', indices=indices)
340        stage = self.get_quantity('stage').\
341                get_values(location='centroids', indices=indices)       
342        depth = stage - elevation
343
344        # Select indices for which depth > 0
345        wet_indices = num.compress(depth > minimum_allowed_height,
346                                   num.arange(len(depth)))
347        return wet_indices
348
349
350    def get_maximum_inundation_elevation(self, indices=None):
351        """Return highest elevation where h > 0
352
353        Optional argument:
354            indices is the set of element ids that the operation applies to.
355
356        Usage:
357            q = get_maximum_inundation_elevation()
358
359        Note, centroid values are used for this operation           
360        """
361
362        wet_elements = self.get_wet_elements(indices)
363        return self.get_quantity('elevation').\
364               get_maximum_value(indices=wet_elements)
365
366
367    def get_maximum_inundation_location(self, indices=None):
368        """Return location of highest elevation where h > 0
369
370        Optional argument:
371            indices is the set of element ids that the operation applies to.
372
373        Usage:
374            q = get_maximum_inundation_location()
375
376        Note, centroid values are used for this operation           
377        """
378
379        wet_elements = self.get_wet_elements(indices)
380        return self.get_quantity('elevation').\
381               get_maximum_location(indices=wet_elements)   
382               
383               
384               
385               
386    def get_flow_through_cross_section(self, polyline,
387                                       verbose=False):               
388        """Get the total flow through an arbitrary poly line.       
389       
390        This is a run-time equivalent of the function with same name
391        in data_manager.py
392       
393        Input:
394            polyline: Representation of desired cross section - it may contain
395                      multiple sections allowing for complex shapes. Assume
396                      absolute UTM coordinates.
397                      Format [[x0, y0], [x1, y1], ...]       
398                 
399        Output:       
400            Q: Total flow [m^3/s] across given segments.
401       
402         
403        """       
404       
405        # Find all intersections and associated triangles.
406        segments = self.get_intersecting_segments(polyline, 
407                                                  use_cache=True,
408                                                  verbose=verbose)
409
410        # Get midpoints
411        midpoints = segment_midpoints(segments)       
412       
413        # Make midpoints Geospatial instances
414        midpoints = ensure_geospatial(midpoints, self.geo_reference)       
415       
416        # Compute flow       
417        if verbose: print 'Computing flow through specified cross section'
418       
419        # Get interpolated values
420        xmomentum = self.get_quantity('xmomentum')
421        ymomentum = self.get_quantity('ymomentum')       
422       
423        uh = xmomentum.get_values(interpolation_points=midpoints, use_cache=True)
424        vh = ymomentum.get_values(interpolation_points=midpoints, use_cache=True)
425       
426        # Compute and sum flows across each segment
427        total_flow=0
428        for i in range(len(uh)):
429           
430            # Inner product of momentum vector with segment normal [m^2/s]
431            normal = segments[i].normal
432            normal_momentum = uh[i]*normal[0] + vh[i]*normal[1] 
433               
434            # Flow across this segment [m^3/s]
435            segment_flow = normal_momentum*segments[i].length
436
437            # Accumulate
438            total_flow += segment_flow
439           
440        return total_flow
441
442       
443               
444    def get_energy_through_cross_section(self, polyline,
445                                         kind='total',
446                                         verbose=False):               
447        """Obtain average energy head [m] across specified cross section.
448
449        Inputs:
450            polyline: Representation of desired cross section - it may contain
451                      multiple sections allowing for complex shapes. Assume
452                      absolute UTM coordinates.
453                      Format [[x0, y0], [x1, y1], ...]
454            kind:     Select which energy to compute.
455                      Options are 'specific' and 'total' (default)
456
457        Output:
458            E: Average energy [m] across given segments for all stored times.
459
460        The average velocity is computed for each triangle intersected by
461        the polyline and averaged weighted by segment lengths.
462
463        The typical usage of this function would be to get average energy of
464        flow in a channel, and the polyline would then be a cross section
465        perpendicular to the flow.
466
467        #FIXME (Ole) - need name for this energy reflecting that its dimension
468        is [m].
469        """
470
471        from anuga.config import g, epsilon, velocity_protection as h0       
472                                         
473       
474        # Find all intersections and associated triangles.
475        segments = self.get_intersecting_segments(polyline, 
476                                                  use_cache=True,
477                                                  verbose=verbose)
478
479        # Get midpoints
480        midpoints = segment_midpoints(segments)       
481       
482        # Make midpoints Geospatial instances
483        midpoints = ensure_geospatial(midpoints, self.geo_reference)       
484       
485        # Compute energy       
486        if verbose: print 'Computing %s energy' %kind       
487       
488        # Get interpolated values
489        stage = self.get_quantity('stage')       
490        elevation = self.get_quantity('elevation')               
491        xmomentum = self.get_quantity('xmomentum')
492        ymomentum = self.get_quantity('ymomentum')       
493
494        w = stage.get_values(interpolation_points=midpoints, use_cache=True)
495        z = elevation.get_values(interpolation_points=midpoints, use_cache=True)       
496        uh = xmomentum.get_values(interpolation_points=midpoints, use_cache=True)
497        vh = ymomentum.get_values(interpolation_points=midpoints, use_cache=True)
498        h = w-z # Depth
499       
500        # Compute total length of polyline for use with weighted averages
501        total_line_length = 0.0
502        for segment in segments:
503            total_line_length += segment.length
504       
505        # Compute and sum flows across each segment
506        average_energy=0.0
507        for i in range(len(w)):
508           
509            # Average velocity across this segment
510            if h[i] > epsilon:
511                # Use protection against degenerate velocities
512                u = uh[i]/(h[i] + h0/h[i])
513                v = vh[i]/(h[i] + h0/h[i])
514            else:
515                u = v = 0.0
516               
517            speed_squared = u*u + v*v   
518            kinetic_energy = 0.5*speed_squared/g
519           
520            if kind == 'specific':
521                segment_energy = h[i] + kinetic_energy
522            elif kind == 'total':
523                segment_energy = w[i] + kinetic_energy               
524            else:
525                msg = 'Energy kind must be either "specific" or "total".'
526                msg += ' I got %s' %kind
527               
528
529            # Add to weighted average
530            weigth = segments[i].length/total_line_length
531            average_energy += segment_energy*weigth
532             
533           
534        return average_energy
535       
536
537                       
538
539    def check_integrity(self):
540        Generic_Domain.check_integrity(self)
541
542        #Check that we are solving the shallow water wave equation
543
544        msg = 'First conserved quantity must be "stage"'
545        assert self.conserved_quantities[0] == 'stage', msg
546        msg = 'Second conserved quantity must be "xmomentum"'
547        assert self.conserved_quantities[1] == 'xmomentum', msg
548        msg = 'Third conserved quantity must be "ymomentum"'
549        assert self.conserved_quantities[2] == 'ymomentum', msg
550
551    def extrapolate_second_order_sw(self):
552        #Call correct module function
553        #(either from this module or C-extension)
554        extrapolate_second_order_sw(self)
555
556    def compute_fluxes(self):
557        #Call correct module function
558        #(either from this module or C-extension)
559        compute_fluxes(self)
560
561    def distribute_to_vertices_and_edges(self):
562        # Call correct module function
563        if self.use_edge_limiter:
564            distribute_using_edge_limiter(self)           
565        else:
566            distribute_using_vertex_limiter(self)
567
568
569
570
571    def evolve(self,
572               yieldstep = None,
573               finaltime = None,
574               duration = None,
575               skip_initial_step = False):
576        """Specialisation of basic evolve method from parent class
577        """
578
579        # Call check integrity here rather than from user scripts
580        # self.check_integrity()
581
582        msg = 'Parameter beta_w must be in the interval [0, 2['
583        assert 0 <= self.beta_w <= 2.0, msg
584
585
586        # Initial update of vertex and edge values before any STORAGE
587        # and or visualisation
588        # This is done again in the initialisation of the Generic_Domain
589        # evolve loop but we do it here to ensure the values are ok for storage
590        self.distribute_to_vertices_and_edges()
591
592        if self.store is True and self.time == 0.0:
593            self.initialise_storage()
594            # print 'Storing results in ' + self.writer.filename
595        else:
596            pass
597            # print 'Results will not be stored.'
598            # print 'To store results set domain.store = True'
599            # FIXME: Diagnostic output should be controlled by
600            # a 'verbose' flag living in domain (or in a parent class)
601
602        # Call basic machinery from parent class
603        for t in Generic_Domain.evolve(self,
604                                       yieldstep=yieldstep,
605                                       finaltime=finaltime,
606                                       duration=duration,
607                                       skip_initial_step=skip_initial_step):
608
609            # Store model data, e.g. for subsequent visualisation
610            if self.store is True:
611                self.store_timestep(self.quantities_to_be_stored)
612
613            # FIXME: Could maybe be taken from specified list
614            # of 'store every step' quantities
615
616            # Pass control on to outer loop for more specific actions
617            yield(t)
618
619
620    def initialise_storage(self):
621        """Create and initialise self.writer object for storing data.
622        Also, save x,y and bed elevation
623        """
624
625        from anuga.shallow_water.data_manager import get_dataobject
626
627        # Initialise writer
628        self.writer = get_dataobject(self, mode=netcdf_mode_w)
629
630        # Store vertices and connectivity
631        self.writer.store_connectivity()
632
633
634    def store_timestep(self, name):
635        """Store named quantity and time.
636
637        Precondition:
638           self.write has been initialised
639        """
640        self.writer.store_timestep(name)
641
642       
643    def timestepping_statistics(self,
644                                track_speeds=False,
645                                triangle_id=None):       
646        """Return string with time stepping statistics for printing or logging
647
648        Optional boolean keyword track_speeds decides whether to report
649        location of smallest timestep as well as a histogram and percentile
650        report.
651        """
652
653        from anuga.config import epsilon, g               
654
655
656        # Call basic machinery from parent class
657        msg = Generic_Domain.timestepping_statistics(self,
658                                                     track_speeds,
659                                                     triangle_id)
660
661        if track_speeds is True:
662
663            # qwidth determines the text field used for quantities
664            qwidth = self.qwidth
665       
666            # Selected triangle
667            k = self.k
668
669            # Report some derived quantities at vertices, edges and centroid
670            # specific to the shallow water wave equation
671
672            z = self.quantities['elevation']
673            w = self.quantities['stage']           
674
675            Vw = w.get_values(location='vertices', indices=[k])[0]
676            Ew = w.get_values(location='edges', indices=[k])[0]
677            Cw = w.get_values(location='centroids', indices=[k])
678
679            Vz = z.get_values(location='vertices', indices=[k])[0]
680            Ez = z.get_values(location='edges', indices=[k])[0]
681            Cz = z.get_values(location='centroids', indices=[k])                             
682               
683
684            name = 'depth'
685            Vh = Vw-Vz
686            Eh = Ew-Ez
687            Ch = Cw-Cz
688           
689            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
690                 %(name.ljust(qwidth), Vh[0], Vh[1], Vh[2])
691           
692            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
693                 %(name.ljust(qwidth), Eh[0], Eh[1], Eh[2])
694           
695            s += '    %s: centroid_value = %.4f\n'\
696                 %(name.ljust(qwidth), Ch[0])                               
697           
698            msg += s
699
700            uh = self.quantities['xmomentum']
701            vh = self.quantities['ymomentum']
702
703            Vuh = uh.get_values(location='vertices', indices=[k])[0]
704            Euh = uh.get_values(location='edges', indices=[k])[0]
705            Cuh = uh.get_values(location='centroids', indices=[k])
706           
707            Vvh = vh.get_values(location='vertices', indices=[k])[0]
708            Evh = vh.get_values(location='edges', indices=[k])[0]
709            Cvh = vh.get_values(location='centroids', indices=[k])
710
711            # Speeds in each direction
712            Vu = Vuh/(Vh + epsilon)
713            Eu = Euh/(Eh + epsilon)
714            Cu = Cuh/(Ch + epsilon)           
715            name = 'U'
716            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
717                 %(name.ljust(qwidth), Vu[0], Vu[1], Vu[2])
718           
719            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
720                 %(name.ljust(qwidth), Eu[0], Eu[1], Eu[2])
721           
722            s += '    %s: centroid_value = %.4f\n'\
723                 %(name.ljust(qwidth), Cu[0])                               
724           
725            msg += s
726
727            Vv = Vvh/(Vh + epsilon)
728            Ev = Evh/(Eh + epsilon)
729            Cv = Cvh/(Ch + epsilon)           
730            name = 'V'
731            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
732                 %(name.ljust(qwidth), Vv[0], Vv[1], Vv[2])
733           
734            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
735                 %(name.ljust(qwidth), Ev[0], Ev[1], Ev[2])
736           
737            s += '    %s: centroid_value = %.4f\n'\
738                 %(name.ljust(qwidth), Cv[0])                               
739           
740            msg += s
741
742
743            # Froude number in each direction
744            name = 'Froude (x)'
745            Vfx = Vu/(num.sqrt(g*Vh) + epsilon)
746            Efx = Eu/(num.sqrt(g*Eh) + epsilon)
747            Cfx = Cu/(num.sqrt(g*Ch) + epsilon)
748           
749            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
750                 %(name.ljust(qwidth), Vfx[0], Vfx[1], Vfx[2])
751           
752            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
753                 %(name.ljust(qwidth), Efx[0], Efx[1], Efx[2])
754           
755            s += '    %s: centroid_value = %.4f\n'\
756                 %(name.ljust(qwidth), Cfx[0])                               
757           
758            msg += s
759
760
761            name = 'Froude (y)'
762            Vfy = Vv/(num.sqrt(g*Vh) + epsilon)
763            Efy = Ev/(num.sqrt(g*Eh) + epsilon)
764            Cfy = Cv/(num.sqrt(g*Ch) + epsilon)
765           
766            s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
767                 %(name.ljust(qwidth), Vfy[0], Vfy[1], Vfy[2])
768           
769            s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
770                 %(name.ljust(qwidth), Efy[0], Efy[1], Efy[2])
771           
772            s += '    %s: centroid_value = %.4f\n'\
773                 %(name.ljust(qwidth), Cfy[0])                               
774           
775            msg += s           
776
777               
778
779        return msg
780       
781       
782
783    def compute_volumetric_balance(self):
784        """Compute volumetric balance at current timestep.
785                       
786        Quantities computed are:
787           Total inflow across boundary
788           Total outflow across boundary
789           Total inflow through forcing terms
790           Total outflow through forcing terms
791           Current total volume in domain
792       
793                       
794        """
795               
796        # Run through boundary array and compute for each segment
797        # the normal momentum ((uh, vh) dot normal) times segment length.
798        # Based on sign accumulate this into boundary_inflow and boundary_outflow.
799        # The go through explicit forcing update and record the rate of change for stage and
800        # record into forcing_inflow and forcing_outflow. Finally compute integral
801        # of depth to obtain total volume of domain.
802                       
803                       
804        # Compute flows along boundary
805       
806        uh = self.get_quantity('xmomentum').get_values()
807        vh = self.get_quantity('ymomentum').get_values()       
808       
809        # Loop through edges that lie on the boundary and calculate
810        # flows
811        inflow = 0.0
812        outflow = 0.0
813        for vol_id, edge_id in self.boundary:
814            print vol_id, edge_id, self.boundary[(vol_id, edge_id)]
815
816            # Pick edge and compute normal flow
817            print uh[vol_id, :]
818            print vh[vol_id, :]           
819         
820       
821       
822               
823               
824#=============== End of class Shallow Water Domain ===============================
825
826
827#-----------------
828# Flux computation
829#-----------------
830
831def compute_fluxes(domain):
832    """Compute all fluxes and the timestep suitable for all volumes
833    in domain.
834
835    Compute total flux for each conserved quantity using "flux_function"
836
837    Fluxes across each edge are scaled by edgelengths and summed up
838    Resulting flux is then scaled by area and stored in
839    explicit_update for each of the three conserved quantities
840    stage, xmomentum and ymomentum
841
842    The maximal allowable speed computed by the flux_function for each volume
843    is converted to a timestep that must not be exceeded. The minimum of
844    those is computed as the next overall timestep.
845
846    Post conditions:
847      domain.explicit_update is reset to computed flux values
848      domain.timestep is set to the largest step satisfying all volumes.
849   
850
851    This wrapper calls the underlying C version of compute fluxes
852    """
853
854    import sys
855
856    N = len(domain) # number_of_triangles
857
858    # Shortcuts
859    Stage = domain.quantities['stage']
860    Xmom = domain.quantities['xmomentum']
861    Ymom = domain.quantities['ymomentum']
862    Bed = domain.quantities['elevation']
863
864    timestep = float(sys.maxint)
865    from shallow_water_ext import\
866         compute_fluxes_ext_central as compute_fluxes_ext
867
868
869    flux_timestep = compute_fluxes_ext(timestep,
870                                       domain.epsilon,
871                                       domain.H0,
872                                       domain.g,
873                                       domain.neighbours,
874                                       domain.neighbour_edges,
875                                       domain.normals,
876                                       domain.edgelengths,
877                                       domain.radii,
878                                       domain.areas,
879                                       domain.tri_full_flag,
880                                       Stage.edge_values,
881                                       Xmom.edge_values,
882                                       Ymom.edge_values,
883                                       Bed.edge_values,
884                                       Stage.boundary_values,
885                                       Xmom.boundary_values,
886                                       Ymom.boundary_values,
887                                       Stage.explicit_update,
888                                       Xmom.explicit_update,
889                                       Ymom.explicit_update,
890                                       domain.already_computed_flux,
891                                       domain.max_speed,
892                                       int(domain.optimise_dry_cells))
893
894    domain.flux_timestep = flux_timestep
895
896
897
898#---------------------------------------
899# Module functions for gradient limiting
900#---------------------------------------
901
902
903# MH090605 The following method belongs to the shallow_water domain class
904# see comments in the corresponding method in shallow_water_ext.c
905def extrapolate_second_order_sw(domain):
906    """Wrapper calling C version of extrapolate_second_order_sw
907    """
908    import sys
909
910    N = len(domain) # number_of_triangles
911
912    # Shortcuts
913    Stage = domain.quantities['stage']
914    Xmom = domain.quantities['xmomentum']
915    Ymom = domain.quantities['ymomentum']
916    Elevation = domain.quantities['elevation']
917
918    from shallow_water_ext import extrapolate_second_order_sw as extrapol2
919    extrapol2(domain,
920              domain.surrogate_neighbours,
921              domain.number_of_boundaries,
922              domain.centroid_coordinates,
923              Stage.centroid_values,
924              Xmom.centroid_values,
925              Ymom.centroid_values,
926              Elevation.centroid_values,
927              domain.vertex_coordinates,
928              Stage.vertex_values,
929              Xmom.vertex_values,
930              Ymom.vertex_values,
931              Elevation.vertex_values,
932              int(domain.optimise_dry_cells))
933
934
935def distribute_using_vertex_limiter(domain):
936    """Distribution from centroids to vertices specific to the
937    shallow water wave
938    equation.
939
940    It will ensure that h (w-z) is always non-negative even in the
941    presence of steep bed-slopes by taking a weighted average between shallow
942    and deep cases.
943
944    In addition, all conserved quantities get distributed as per either a
945    constant (order==1) or a piecewise linear function (order==2).
946
947    FIXME: more explanation about removal of artificial variability etc
948
949    Precondition:
950      All quantities defined at centroids and bed elevation defined at
951      vertices.
952
953    Postcondition
954      Conserved quantities defined at vertices
955
956    """
957
958   
959
960    # Remove very thin layers of water
961    protect_against_infinitesimal_and_negative_heights(domain)
962
963    # Extrapolate all conserved quantities
964    if domain.optimised_gradient_limiter:
965        # MH090605 if second order,
966        # perform the extrapolation and limiting on
967        # all of the conserved quantities
968
969        if (domain._order_ == 1):
970            for name in domain.conserved_quantities:
971                Q = domain.quantities[name]
972                Q.extrapolate_first_order()
973        elif domain._order_ == 2:
974            domain.extrapolate_second_order_sw()
975        else:
976            raise 'Unknown order'
977    else:
978        # Old code:
979        for name in domain.conserved_quantities:
980            Q = domain.quantities[name]
981
982            if domain._order_ == 1:
983                Q.extrapolate_first_order()
984            elif domain._order_ == 2:
985                Q.extrapolate_second_order_and_limit_by_vertex()
986            else:
987                raise 'Unknown order'
988
989
990    # Take bed elevation into account when water heights are small
991    balance_deep_and_shallow(domain)
992
993    # Compute edge values by interpolation
994    for name in domain.conserved_quantities:
995        Q = domain.quantities[name]
996        Q.interpolate_from_vertices_to_edges()
997
998
999
1000def distribute_using_edge_limiter(domain):
1001    """Distribution from centroids to edges specific to the
1002    shallow water wave
1003    equation.
1004
1005    It will ensure that h (w-z) is always non-negative even in the
1006    presence of steep bed-slopes by taking a weighted average between shallow
1007    and deep cases.
1008
1009    In addition, all conserved quantities get distributed as per either a
1010    constant (order==1) or a piecewise linear function (order==2).
1011
1012
1013    Precondition:
1014      All quantities defined at centroids and bed elevation defined at
1015      vertices.
1016
1017    Postcondition
1018      Conserved quantities defined at vertices
1019
1020    """
1021
1022    # Remove very thin layers of water
1023    protect_against_infinitesimal_and_negative_heights(domain)
1024
1025
1026    for name in domain.conserved_quantities:
1027        Q = domain.quantities[name]
1028        if domain._order_ == 1:
1029            Q.extrapolate_first_order()
1030        elif domain._order_ == 2:
1031            Q.extrapolate_second_order_and_limit_by_edge()
1032        else:
1033            raise 'Unknown order'
1034
1035    balance_deep_and_shallow(domain)
1036
1037    # Compute edge values by interpolation
1038    for name in domain.conserved_quantities:
1039        Q = domain.quantities[name]
1040        Q.interpolate_from_vertices_to_edges()
1041
1042
1043def protect_against_infinitesimal_and_negative_heights(domain):
1044    """Protect against infinitesimal heights and associated high velocities
1045    """
1046
1047    # Shortcuts
1048    wc = domain.quantities['stage'].centroid_values
1049    zc = domain.quantities['elevation'].centroid_values
1050    xmomc = domain.quantities['xmomentum'].centroid_values
1051    ymomc = domain.quantities['ymomentum'].centroid_values
1052
1053    from shallow_water_ext import protect
1054
1055    protect(domain.minimum_allowed_height, domain.maximum_allowed_speed,
1056            domain.epsilon, wc, zc, xmomc, ymomc)
1057
1058
1059
1060def balance_deep_and_shallow(domain):
1061    """Compute linear combination between stage as computed by
1062    gradient-limiters limiting using w, and stage computed by
1063    gradient-limiters limiting using h (h-limiter).
1064    The former takes precedence when heights are large compared to the
1065    bed slope while the latter takes precedence when heights are
1066    relatively small.  Anything in between is computed as a balanced
1067    linear combination in order to avoid numerical disturbances which
1068    would otherwise appear as a result of hard switching between
1069    modes.
1070
1071    Wrapper for C implementation
1072    """
1073
1074    from shallow_water_ext import balance_deep_and_shallow as balance_deep_and_shallow_c
1075
1076
1077    # Shortcuts
1078    wc = domain.quantities['stage'].centroid_values
1079    zc = domain.quantities['elevation'].centroid_values
1080
1081    wv = domain.quantities['stage'].vertex_values
1082    zv = domain.quantities['elevation'].vertex_values
1083
1084    # Momentums at centroids
1085    xmomc = domain.quantities['xmomentum'].centroid_values
1086    ymomc = domain.quantities['ymomentum'].centroid_values
1087
1088    # Momentums at vertices
1089    xmomv = domain.quantities['xmomentum'].vertex_values
1090    ymomv = domain.quantities['ymomentum'].vertex_values
1091
1092    balance_deep_and_shallow_c(domain,
1093                               wc, zc, wv, zv, wc, 
1094                               xmomc, ymomc, xmomv, ymomv)
1095
1096
1097
1098
1099#------------------------------------------------------------------
1100# Boundary conditions - specific to the shallow water wave equation
1101#------------------------------------------------------------------
1102class Reflective_boundary(Boundary):
1103    """Reflective boundary returns same conserved quantities as
1104    those present in its neighbour volume but reflected.
1105
1106    This class is specific to the shallow water equation as it
1107    works with the momentum quantities assumed to be the second
1108    and third conserved quantities.
1109    """
1110
1111    def __init__(self, domain = None):
1112        Boundary.__init__(self)
1113
1114        if domain is None:
1115            msg = 'Domain must be specified for reflective boundary'
1116            raise msg
1117
1118        # Handy shorthands
1119        self.stage   = domain.quantities['stage'].edge_values
1120        self.xmom    = domain.quantities['xmomentum'].edge_values
1121        self.ymom    = domain.quantities['ymomentum'].edge_values
1122        self.normals = domain.normals
1123
1124        self.conserved_quantities = num.zeros(3, num.Float)
1125
1126    def __repr__(self):
1127        return 'Reflective_boundary'
1128
1129
1130    def evaluate(self, vol_id, edge_id):
1131        """Reflective boundaries reverses the outward momentum
1132        of the volume they serve.
1133        """
1134
1135        q = self.conserved_quantities
1136        q[0] = self.stage[vol_id, edge_id]
1137        q[1] = self.xmom[vol_id, edge_id]
1138        q[2] = self.ymom[vol_id, edge_id]
1139
1140        normal = self.normals[vol_id, 2*edge_id:2*edge_id+2]
1141
1142
1143        r = rotate(q, normal, direction = 1)
1144        r[1] = -r[1]
1145        q = rotate(r, normal, direction = -1)
1146
1147        return q
1148
1149
1150
1151
1152class Transmissive_momentum_set_stage_boundary(Boundary):
1153    """Returns same momentum conserved quantities as
1154    those present in its neighbour volume.
1155    Sets stage by specifying a function f of time which may either be a
1156    vector function or a scalar function
1157
1158    Example:
1159
1160    def waveform(t):
1161        return sea_level + normalized_amplitude/cosh(t-25)**2
1162
1163    Bts = Transmissive_momentum_set_stage_boundary(domain, waveform)
1164   
1165
1166    Underlying domain must be specified when boundary is instantiated
1167    """
1168
1169    def __init__(self, domain = None, function=None):
1170        Boundary.__init__(self)
1171
1172        if domain is None:
1173            msg = 'Domain must be specified for this type boundary'
1174            raise msg
1175
1176        if function is None:
1177            msg = 'Function must be specified for this type boundary'
1178            raise msg
1179
1180        self.domain   = domain
1181        self.function = function
1182
1183    def __repr__(self):
1184        return 'Transmissive_momentum_set_stage_boundary(%s)' %self.domain
1185
1186    def evaluate(self, vol_id, edge_id):
1187        """Transmissive momentum set stage boundaries return the edge momentum
1188        values of the volume they serve.
1189        """
1190
1191        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
1192
1193
1194        t = self.domain.get_time()
1195
1196        if hasattr(self.function, 'time'):
1197            # Roll boundary over if time exceeds           
1198            while t > self.function.time[-1]:
1199                msg = 'WARNING: domain time %.2f has exceeded' %t
1200                msg += 'time provided in '
1201                msg += 'transmissive_momentum_set_stage boundary object.\n'
1202                msg += 'I will continue, reusing the object from t==0'
1203                print msg
1204                t -= self.function.time[-1]
1205
1206
1207        value = self.function(t)
1208        try:
1209            x = float(value)
1210        except:
1211            x = float(value[0])
1212           
1213        q[0] = x
1214        return q
1215
1216
1217        # FIXME: Consider this (taken from File_boundary) to allow
1218        # spatial variation
1219        # if vol_id is not None and edge_id is not None:
1220        #     i = self.boundary_indices[ vol_id, edge_id ]
1221        #     return self.F(t, point_id = i)
1222        # else:
1223        #     return self.F(t)
1224
1225
1226# Backward compatibility       
1227# FIXME(Ole): Deprecate
1228class Transmissive_Momentum_Set_Stage_boundary(Transmissive_momentum_set_stage_boundary):
1229    pass
1230
1231     
1232
1233class Transmissive_stage_zero_momentum_boundary(Boundary):
1234    """Return same stage as those present in its neighbour volume. Set momentum to zero.
1235
1236    Underlying domain must be specified when boundary is instantiated
1237    """
1238
1239    def __init__(self, domain=None):
1240        Boundary.__init__(self)
1241
1242        if domain is None:
1243            msg = 'Domain must be specified for '
1244            msg += 'Transmissive_stage_zero_momentum boundary'
1245            raise Exception, msg
1246
1247        self.domain = domain
1248
1249    def __repr__(self):
1250        return 'Transmissive_stage_zero_momentum_boundary(%s)' %self.domain
1251
1252    def evaluate(self, vol_id, edge_id):
1253        """Transmissive boundaries return the edge values
1254        of the volume they serve.
1255        """
1256
1257        q = self.domain.get_conserved_quantities(vol_id, edge=edge_id)
1258       
1259        q[1] = q[2] = 0.0
1260        return q
1261
1262
1263       
1264class Dirichlet_discharge_boundary(Boundary):
1265    """
1266    Sets stage (stage0)
1267    Sets momentum (wh0) in the inward normal direction.
1268
1269    Underlying domain must be specified when boundary is instantiated
1270    """
1271
1272    def __init__(self, domain=None, stage0=None, wh0=None):
1273        Boundary.__init__(self)
1274
1275        if domain is None:
1276            msg = 'Domain must be specified for this type boundary'
1277            raise msg
1278
1279        if stage0 is None:
1280            raise 'set stage'
1281
1282        if wh0 is None:
1283            wh0 = 0.0
1284
1285        self.domain   = domain
1286        self.stage0  = stage0
1287        self.wh0 = wh0
1288
1289    def __repr__(self):
1290        return 'Dirichlet_Discharge_boundary(%s)' %self.domain
1291
1292    def evaluate(self, vol_id, edge_id):
1293        """Set discharge in the (inward) normal direction
1294        """
1295
1296        normal = self.domain.get_normal(vol_id,edge_id)
1297        q = [self.stage0, -self.wh0*normal[0], -self.wh0*normal[1]]
1298        return q
1299
1300
1301        # FIXME: Consider this (taken from File_boundary) to allow
1302        # spatial variation
1303        # if vol_id is not None and edge_id is not None:
1304        #     i = self.boundary_indices[ vol_id, edge_id ]
1305        #     return self.F(t, point_id = i)
1306        # else:
1307        #     return self.F(t)
1308
1309
1310       
1311# Backward compatibility       
1312# FIXME(Ole): Deprecate
1313class Dirichlet_Discharge_boundary(Dirichlet_discharge_boundary):
1314    pass
1315                                                   
1316   
1317
1318       
1319       
1320class Field_boundary(Boundary):
1321    """Set boundary from given field represented in an sww file containing values
1322    for stage, xmomentum and ymomentum.
1323    Optionally, the user can specify mean_stage to offset the stage provided in the
1324    sww file.
1325
1326    This function is a thin wrapper around the generic File_boundary. The
1327    difference between the file_boundary and field_boundary is only that the
1328    field_boundary will allow you to change the level of the stage height when
1329    you read in the boundary condition. This is very useful when running
1330    different tide heights in the same area as you need only to convert one
1331    boundary condition to a SWW file, ideally for tide height of 0 m
1332    (saving disk space). Then you can use field_boundary to read this SWW file
1333    and change the stage height (tide) on the fly depending on the scenario.
1334   
1335    """
1336
1337
1338    def __init__(self, filename, domain,
1339                 mean_stage=0.0,
1340                 time_thinning=1,
1341                 time_limit=None,
1342                 boundary_polygon=None,   
1343                 default_boundary=None,                 
1344                 use_cache=False,
1345                 verbose=False):
1346        """Constructor
1347
1348        filename: Name of sww file
1349        domain: pointer to shallow water domain for which the boundary applies
1350        mean_stage: The mean water level which will be added to stage derived
1351                    from the boundary condition
1352        time_thinning: Will set how many time steps from the sww file read in
1353                       will be interpolated to the boundary. For example if
1354                       the sww file has 1 second time steps and is 24 hours
1355                       in length it has 86400 time steps. If you set
1356                       time_thinning to 1 it will read all these steps.
1357                       If you set it to 100 it will read every 100th step eg
1358                       only 864 step. This parameter is very useful to increase
1359                       the speed of a model run that you are setting up
1360                       and testing.
1361                       
1362        default_boundary: Must be either None or an instance of a
1363                          class descending from class Boundary.
1364                          This will be used in case model time exceeds
1365                          that available in the underlying data.
1366                          Note that mean_stage will also be added to this.
1367                                               
1368        use_cache:
1369        verbose:
1370       
1371        """
1372
1373        # Create generic file_boundary object
1374        self.file_boundary = File_boundary(filename, domain,
1375                                           time_thinning=time_thinning,
1376                                           time_limit=time_limit,
1377                                           boundary_polygon=boundary_polygon,
1378                                           default_boundary=default_boundary,
1379                                           use_cache=use_cache,
1380                                           verbose=verbose)
1381
1382       
1383        # Record information from File_boundary
1384        self.F = self.file_boundary.F
1385        self.domain = self.file_boundary.domain
1386       
1387        # Record mean stage
1388        self.mean_stage = mean_stage
1389
1390
1391    def __repr__(self):
1392        return 'Field boundary'
1393
1394
1395    def evaluate(self, vol_id=None, edge_id=None):
1396        """Return linearly interpolated values based on domain.time
1397
1398        vol_id and edge_id are ignored
1399        """
1400       
1401        # Evaluate file boundary
1402        q = self.file_boundary.evaluate(vol_id, edge_id)
1403
1404        # Adjust stage
1405        for j, name in enumerate(self.domain.conserved_quantities):
1406            if name == 'stage':
1407                q[j] += self.mean_stage
1408        return q
1409
1410   
1411
1412#-----------------------
1413# Standard forcing terms
1414#-----------------------
1415
1416def gravity(domain):
1417    """Apply gravitational pull in the presence of bed slope
1418    Wrapper calls underlying C implementation
1419    """
1420
1421    xmom = domain.quantities['xmomentum'].explicit_update
1422    ymom = domain.quantities['ymomentum'].explicit_update
1423
1424    stage = domain.quantities['stage']
1425    elevation = domain.quantities['elevation']
1426
1427    h = stage.centroid_values - elevation.centroid_values
1428    z = elevation.vertex_values
1429
1430    x = domain.get_vertex_coordinates()
1431    g = domain.g
1432   
1433
1434    from shallow_water_ext import gravity as gravity_c
1435    gravity_c(g, h, z, x, xmom, ymom) #, 1.0e-6)
1436
1437
1438
1439def manning_friction_implicit(domain):
1440    """Apply (Manning) friction to water momentum   
1441    Wrapper for c version
1442    """
1443
1444
1445    #print 'Implicit friction'
1446
1447    xmom = domain.quantities['xmomentum']
1448    ymom = domain.quantities['ymomentum']
1449
1450    w = domain.quantities['stage'].centroid_values
1451    z = domain.quantities['elevation'].centroid_values
1452
1453    uh = xmom.centroid_values
1454    vh = ymom.centroid_values
1455    eta = domain.quantities['friction'].centroid_values
1456
1457    xmom_update = xmom.semi_implicit_update
1458    ymom_update = ymom.semi_implicit_update
1459
1460    N = len(domain)
1461    eps = domain.minimum_allowed_height
1462    g = domain.g
1463
1464    from shallow_water_ext import manning_friction as manning_friction_c
1465    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1466
1467
1468def manning_friction_explicit(domain):
1469    """Apply (Manning) friction to water momentum   
1470    Wrapper for c version
1471    """
1472
1473    # print 'Explicit friction'
1474
1475    xmom = domain.quantities['xmomentum']
1476    ymom = domain.quantities['ymomentum']
1477
1478    w = domain.quantities['stage'].centroid_values
1479    z = domain.quantities['elevation'].centroid_values
1480
1481    uh = xmom.centroid_values
1482    vh = ymom.centroid_values
1483    eta = domain.quantities['friction'].centroid_values
1484
1485    xmom_update = xmom.explicit_update
1486    ymom_update = ymom.explicit_update
1487
1488    N = len(domain)
1489    eps = domain.minimum_allowed_height
1490    g = domain.g
1491
1492    from shallow_water_ext import manning_friction as manning_friction_c
1493    manning_friction_c(g, eps, w, z, uh, vh, eta, xmom_update, ymom_update)
1494
1495
1496# FIXME (Ole): This was implemented for use with one of the analytical solutions (Sampson?)
1497# Is it still needed (30 Oct 2007)?
1498def linear_friction(domain):
1499    """Apply linear friction to water momentum
1500
1501    Assumes quantity: 'linear_friction' to be present
1502    """
1503
1504    from math import sqrt
1505
1506    w = domain.quantities['stage'].centroid_values
1507    z = domain.quantities['elevation'].centroid_values
1508    h = w-z
1509
1510    uh = domain.quantities['xmomentum'].centroid_values
1511    vh = domain.quantities['ymomentum'].centroid_values
1512    tau = domain.quantities['linear_friction'].centroid_values
1513
1514    xmom_update = domain.quantities['xmomentum'].semi_implicit_update
1515    ymom_update = domain.quantities['ymomentum'].semi_implicit_update
1516
1517    N = len(domain) # number_of_triangles
1518    eps = domain.minimum_allowed_height
1519    g = domain.g #Not necessary? Why was this added?
1520
1521    for k in range(N):
1522        if tau[k] >= eps:
1523            if h[k] >= eps:
1524                S = -tau[k]/h[k]
1525
1526                #Update momentum
1527                xmom_update[k] += S*uh[k]
1528                ymom_update[k] += S*vh[k]
1529
1530
1531
1532
1533def depth_dependent_friction(domain, default_friction,
1534                             surface_roughness_data,
1535                             verbose=False):
1536    """Returns an array of friction values for each wet element adjusted for depth.
1537
1538    Inputs:
1539        domain - computational domain object
1540        default_friction - depth independent bottom friction
1541        surface_roughness_data - N x 5 array of n0, d1, n1, d2, n2 values for each
1542        friction region.
1543
1544    Outputs:
1545        wet_friction - Array that can be used directly to update friction as follows:
1546                       domain.set_quantity('friction', wet_friction)
1547
1548       
1549       
1550    """
1551
1552    import Numeric as num
1553   
1554    # Create a temp array to store updated depth dependent friction for wet elements
1555    # EHR this is outwardly inneficient but not obvious how to avoid recreating each call??????
1556    N=len(domain)
1557    wet_friction    = num.zeros(N, num.Float)
1558    wet_friction[:] = default_n0   # Initially assign default_n0 to all array so sure have no zeros values
1559   
1560   
1561    depth = domain.create_quantity_from_expression('stage - elevation')  # create depth instance for this timestep
1562    # Recompute depth as vector 
1563    d = depth.get_values(location='centroids')
1564 
1565    # rebuild the 'friction' values adjusted for depth at this instant
1566    for i in domain.get_wet_elements():                                  # loop for each wet element in domain
1567       
1568        # Get roughness data for each element
1569        n0 = float(surface_roughness_data[i,0])
1570        d1 = float(surface_roughness_data[i,1])
1571        n1 = float(surface_roughness_data[i,2])
1572        d2 = float(surface_roughness_data[i,3])
1573        n2 = float(surface_roughness_data[i,4])
1574       
1575       
1576        # Recompute friction values from depth for this element
1577               
1578        if d[i]   <= d1: 
1579            depth_dependent_friction = n1
1580        elif d[i] >= d2:
1581            depth_dependent_friction = n2
1582        else:
1583            depth_dependent_friction = n1+((n2-n1)/(d2-d1))*(d[i]-d1)
1584           
1585        # check sanity of result
1586        if (depth_dependent_friction  < 0.010 or depth_dependent_friction > 9999.0) :
1587            print model_data.basename+' >>>> WARNING: computed depth_dependent friction out of range ddf,n1,n2 ', depth_dependent_friction, n1,n2
1588       
1589        # update depth dependent friction  for that wet element
1590        wet_friction[i] = depth_dependent_friction
1591       
1592    # EHR add code to show range of 'friction across domain at this instant as sanity check?????????
1593   
1594    if verbose :
1595        nvals=domain.get_quantity('friction').get_values(location='centroids')        # return array of domain nvals
1596        n_min=min(nvals)
1597        n_max=max(nvals)
1598       
1599        print "         ++++ calculate_depth_dependent_friction  - Updated friction - range  %7.3f to %7.3f" %(n_min,n_max)
1600   
1601    return wet_friction
1602
1603
1604
1605
1606
1607
1608#---------------------------------
1609# Experimental auxiliary functions
1610#---------------------------------
1611def check_forcefield(f):
1612    """Check that f is either
1613    1: a callable object f(t,x,y), where x and y are vectors
1614       and that it returns an array or a list of same length
1615       as x and y
1616    2: a scalar
1617    """
1618
1619    if callable(f):
1620        N = 3
1621        x = num.ones(3, num.Float)
1622        y = num.ones(3, num.Float)
1623        try:
1624            q = f(1.0, x=x, y=y)
1625        except Exception, e:
1626            msg = 'Function %s could not be executed:\n%s' %(f, e)
1627            # FIXME: Reconsider this semantics
1628            raise msg
1629
1630        try:
1631            q = num.array(q, num.Float)
1632        except:
1633            msg = 'Return value from vector function %s could ' %f
1634            msg += 'not be converted into a Numeric array of floats.\n'
1635            msg += 'Specified function should return either list or array.'
1636            raise msg
1637
1638        # Is this really what we want?
1639        msg = 'Return vector from function %s ' %f
1640        msg += 'must have same lenght as input vectors'
1641        assert len(q) == N, msg
1642
1643    else:
1644        try:
1645            f = float(f)
1646        except:
1647            msg = 'Force field %s must be either a scalar' %f
1648            msg += ' or a vector function'
1649            raise Exception(msg)
1650    return f
1651
1652
1653class Wind_stress:
1654    """Apply wind stress to water momentum in terms of
1655    wind speed [m/s] and wind direction [degrees]
1656    """
1657
1658    def __init__(self, *args, **kwargs):
1659        """Initialise windfield from wind speed s [m/s]
1660        and wind direction phi [degrees]
1661
1662        Inputs v and phi can be either scalars or Python functions, e.g.
1663
1664        W = Wind_stress(10, 178)
1665
1666        #FIXME - 'normal' degrees are assumed for now, i.e. the
1667        vector (1,0) has zero degrees.
1668        We may need to convert from 'compass' degrees later on and also
1669        map from True north to grid north.
1670
1671        Arguments can also be Python functions of t,x,y as in
1672
1673        def speed(t,x,y):
1674            ...
1675            return s
1676
1677        def angle(t,x,y):
1678            ...
1679            return phi
1680
1681        where x and y are vectors.
1682
1683        and then pass the functions in
1684
1685        W = Wind_stress(speed, angle)
1686
1687        The instantiated object W can be appended to the list of
1688        forcing_terms as in
1689
1690        Alternatively, one vector valued function for (speed, angle)
1691        can be applied, providing both quantities simultaneously.
1692        As in
1693        W = Wind_stress(F), where returns (speed, angle) for each t.
1694
1695        domain.forcing_terms.append(W)
1696        """
1697
1698        from anuga.config import rho_a, rho_w, eta_w
1699
1700        if len(args) == 2:
1701            s = args[0]
1702            phi = args[1]
1703        elif len(args) == 1:
1704            # Assume vector function returning (s, phi)(t,x,y)
1705            vector_function = args[0]
1706            s = lambda t,x,y: vector_function(t,x=x,y=y)[0]
1707            phi = lambda t,x,y: vector_function(t,x=x,y=y)[1]
1708        else:
1709           # Assume info is in 2 keyword arguments
1710
1711           if len(kwargs) == 2:
1712               s = kwargs['s']
1713               phi = kwargs['phi']
1714           else:
1715               raise 'Assumes two keyword arguments: s=..., phi=....'
1716
1717        self.speed = check_forcefield(s)
1718        self.phi = check_forcefield(phi)
1719
1720        self.const = eta_w*rho_a/rho_w
1721
1722
1723    def __call__(self, domain):
1724        """Evaluate windfield based on values found in domain
1725        """
1726
1727        from math import pi, cos, sin, sqrt
1728
1729        xmom_update = domain.quantities['xmomentum'].explicit_update
1730        ymom_update = domain.quantities['ymomentum'].explicit_update
1731
1732        N = len(domain) # number_of_triangles
1733        t = domain.time
1734
1735        if callable(self.speed):
1736            xc = domain.get_centroid_coordinates()
1737            s_vec = self.speed(t, xc[:,0], xc[:,1])
1738        else:
1739            # Assume s is a scalar
1740
1741            try:
1742                s_vec = self.speed * num.ones(N, num.Float)
1743            except:
1744                msg = 'Speed must be either callable or a scalar: %s' %self.s
1745                raise msg
1746
1747
1748        if callable(self.phi):
1749            xc = domain.get_centroid_coordinates()
1750            phi_vec = self.phi(t, xc[:,0], xc[:,1])
1751        else:
1752            # Assume phi is a scalar
1753
1754            try:
1755                phi_vec = self.phi * num.ones(N, num.Float)
1756            except:
1757                msg = 'Angle must be either callable or a scalar: %s' %self.phi
1758                raise msg
1759
1760        assign_windfield_values(xmom_update, ymom_update,
1761                                s_vec, phi_vec, self.const)
1762
1763
1764def assign_windfield_values(xmom_update, ymom_update,
1765                            s_vec, phi_vec, const):
1766    """Python version of assigning wind field to update vectors.
1767    A c version also exists (for speed)
1768    """
1769    from math import pi, cos, sin, sqrt
1770
1771    N = len(s_vec)
1772    for k in range(N):
1773        s = s_vec[k]
1774        phi = phi_vec[k]
1775
1776        # Convert to radians
1777        phi = phi*pi/180
1778
1779        # Compute velocity vector (u, v)
1780        u = s*cos(phi)
1781        v = s*sin(phi)
1782
1783        # Compute wind stress
1784        S = const * sqrt(u**2 + v**2)
1785        xmom_update[k] += S*u
1786        ymom_update[k] += S*v
1787
1788
1789
1790
1791
1792class General_forcing:
1793    """General explicit forcing term for update of quantity
1794   
1795    This is used by Inflow and Rainfall for instance
1796   
1797
1798    General_forcing(quantity_name, rate, center, radius, polygon)
1799
1800    domain:     ANUGA computational domain
1801    quantity_name: Name of quantity to update.
1802                   It must be a known conserved quantity.
1803                   
1804    rate [?/s]: Total rate of change over the specified area. 
1805                This parameter can be either a constant or a
1806                function of time. Positive values indicate increases,
1807                negative values indicate decreases.
1808                Rate can be None at initialisation but must be specified
1809                before forcing term is applied (i.e. simulation has started).
1810
1811    center [m]: Coordinates at center of flow point
1812    radius [m]: Size of circular area
1813    polygon:    Arbitrary polygon
1814    default_rate: Rate to be used if rate fails (e.g. if model time exceeds its data)
1815                  Admissible types: None, constant number or function of t
1816
1817
1818    Either center, radius or polygon can be specified but not both.
1819    If neither are specified the entire domain gets updated.
1820    All coordinates to be specified in absolute UTM coordinates (x, y) assuming the zone of domain.   
1821   
1822    Inflow or Rainfall for examples of use
1823    """
1824
1825
1826    # FIXME (AnyOne) : Add various methods to allow spatial variations
1827
1828    def __init__(self,
1829                 domain,
1830                 quantity_name,
1831                 rate=0.0,
1832                 center=None, radius=None,
1833                 polygon=None,
1834                 default_rate=None,
1835                 verbose=False):
1836                     
1837        if center is None:
1838            msg = 'I got radius but no center.'       
1839            assert radius is None, msg
1840           
1841        if radius is None:
1842            msg += 'I got center but no radius.'       
1843            assert center is None, msg
1844           
1845           
1846                     
1847        from math import pi, cos, sin
1848
1849        self.domain = domain
1850        self.quantity_name = quantity_name
1851        self.rate = rate
1852        self.center = ensure_numeric(center)
1853        self.radius = radius
1854        self.polygon = polygon       
1855        self.verbose = verbose
1856        self.value = 0.0 # Can be used to remember value at
1857                         # previous timestep in order to obtain rate
1858
1859        # Get boundary (in absolute coordinates)
1860        bounding_polygon = domain.get_boundary_polygon()
1861
1862
1863        # Update area if applicable
1864        if center is not None and radius is not None:
1865            assert len(center) == 2
1866            msg = 'Polygon cannot be specified when center and radius are'
1867            assert polygon is None, msg
1868
1869
1870            # Check that circle center lies within the mesh.
1871            msg = 'Center %s specified for forcing term did not' %(str(center))
1872            msg += 'fall within the domain boundary.'
1873            assert is_inside_polygon(center, bounding_polygon), msg
1874
1875            # Check that circle periphery lies within the mesh.
1876            N = 100
1877            periphery_points = []
1878            for i in range(N):
1879
1880                theta = 2*pi*i/100
1881               
1882                x = center[0] + radius*cos(theta)
1883                y = center[1] + radius*sin(theta)
1884
1885                periphery_points.append([x,y])
1886
1887
1888            for point in periphery_points:
1889                msg = 'Point %s on periphery for forcing term' %(str(point))
1890                msg += ' did not fall within the domain boundary.'
1891                assert is_inside_polygon(point, bounding_polygon), msg
1892
1893       
1894        if polygon is not None:
1895
1896            # Check that polygon lies within the mesh.
1897            for point in self.polygon:
1898                msg = 'Point %s in polygon for forcing term' %(point)
1899                msg += ' did not fall within the domain boundary.'
1900                assert is_inside_polygon(point, bounding_polygon), msg
1901               
1902                                 
1903
1904        # Pointer to update vector
1905        self.update = domain.quantities[self.quantity_name].explicit_update
1906
1907        # Determine indices in flow area
1908        N = len(domain)   
1909        points = domain.get_centroid_coordinates(absolute=True)
1910
1911        # Calculate indices in exchange area for this forcing term
1912        self.exchange_indices = None
1913        if self.center is not None and self.radius is not None:
1914            # Inlet is circular
1915           
1916            inlet_region = 'center=%s, radius=%s' %(self.center, self.radius) 
1917           
1918            self.exchange_indices = []
1919            for k in range(N):
1920                x, y = points[k,:] # Centroid
1921               
1922                c = self.center
1923                if ((x-c[0])**2+(y-c[1])**2) < self.radius**2:
1924                    self.exchange_indices.append(k)
1925                   
1926        if self.polygon is not None:                   
1927            # Inlet is polygon
1928           
1929            inlet_region = 'polygon=%s' % (self.polygon) 
1930            self.exchange_indices = inside_polygon(points, self.polygon)
1931           
1932           
1933           
1934        if self.exchange_indices is None:
1935            self.exchange_area = polygon_area(bounding_polygon)
1936        else:   
1937            if len(self.exchange_indices) == 0:
1938                msg = 'No triangles have been identified in '
1939                msg += 'specified region: %s' %inlet_region
1940                raise Exception, msg
1941
1942            # Compute exchange area as the sum of areas of triangles identified
1943            # by circle or polygon
1944            self.exchange_area = 0.0
1945            for i in self.exchange_indices:
1946                self.exchange_area += domain.areas[i]
1947           
1948
1949        msg = 'Exchange area in forcing term'
1950        msg += ' has area = %f' %self.exchange_area
1951        assert self.exchange_area > 0.0           
1952           
1953               
1954
1955           
1956        # Check and store default_rate
1957        msg = 'Keyword argument default_rate must be either None ' 
1958        msg += 'or a function of time.\n I got %s' %(str(default_rate)) 
1959        assert default_rate is None or \
1960               type(default_rate) in [IntType, FloatType] or \
1961               callable(default_rate), msg
1962       
1963        if default_rate is not None:
1964
1965            # If it is a constant, make it a function
1966            if not callable(default_rate):
1967                tmp = default_rate
1968                default_rate = lambda t: tmp
1969
1970           
1971            # Check that default_rate is a function of one argument
1972            try:
1973                default_rate(0.0)
1974            except:
1975                raise Exception, msg
1976
1977        self.default_rate = default_rate
1978        self.default_rate_invoked = False    # Flag       
1979       
1980
1981    def __call__(self, domain):
1982        """Apply inflow function at time specified in domain and update stage
1983        """
1984
1985        # Call virtual method allowing local modifications
1986
1987        t = domain.get_time()
1988        try:
1989            rate = self.update_rate(t)
1990        except Modeltime_too_early, e:
1991            raise Modeltime_too_early, e
1992        except Modeltime_too_late, e:
1993            if self.default_rate is None:
1994                raise Exception, e # Reraise exception
1995            else:
1996                # Pass control to default rate function
1997                rate = self.default_rate(t)
1998               
1999                if self.default_rate_invoked is False:
2000                    # Issue warning the first time
2001                    msg = '%s' %str(e)
2002                    msg += 'Instead I will use the default rate: %s\n'\
2003                        %str(self.default_rate) 
2004                    msg += 'Note: Further warnings will be supressed'
2005                    warn(msg)
2006                   
2007                    # FIXME (Ole): Replace this crude flag with
2008                    # Python's ability to print warnings only once.
2009                    # See http://docs.python.org/lib/warning-filter.html
2010                    self.default_rate_invoked = True
2011                   
2012
2013           
2014               
2015
2016        if rate is None:
2017            msg = 'Attribute rate must be specified in General_forcing'
2018            msg += ' or its descendants before attempting to call it'
2019            raise Exception, msg
2020       
2021
2022        # Now rate is a number
2023        if self.verbose is True:
2024            print 'Rate of %s at time = %.2f = %f' %(self.quantity_name,
2025                                                     domain.get_time(),
2026                                                     rate)
2027
2028
2029        if self.exchange_indices is None:
2030            self.update[:] += rate
2031        else:
2032            # Brute force assignment of restricted rate
2033            for k in self.exchange_indices:
2034                self.update[k] += rate
2035
2036
2037    def update_rate(self, t):
2038        """Virtual method allowing local modifications by writing an
2039        overriding version in descendant
2040       
2041        """
2042        if callable(self.rate):
2043            rate = self.rate(t)
2044        else:
2045            rate = self.rate
2046
2047        return rate
2048
2049
2050    def get_quantity_values(self, quantity_name=None):
2051        """Return values for specified quantity restricted to opening
2052       
2053        Optionally a quantity name can be specified if values from another
2054        quantity is sought
2055        """
2056       
2057        if quantity_name is None:
2058            quantity_name = self.quantity_name
2059           
2060        q = self.domain.quantities[quantity_name]
2061        return q.get_values(location='centroids', 
2062                            indices=self.exchange_indices)
2063   
2064
2065    def set_quantity_values(self, val, quantity_name=None):
2066        """Set values for specified quantity restricted to opening
2067       
2068        Optionally a quantity name can be specified if values from another
2069        quantity is sought       
2070        """
2071
2072        if quantity_name is None:
2073            quantity_name = self.quantity_name
2074                   
2075        q = self.domain.quantities[self.quantity_name]               
2076        q.set_values(val, 
2077                     location='centroids', 
2078                     indices=self.exchange_indices)   
2079
2080
2081
2082class Rainfall(General_forcing):
2083    """Class Rainfall - general 'rain over entire domain' forcing term.
2084   
2085    Used for implementing Rainfall over the entire domain.
2086       
2087        Current Limited to only One Gauge..
2088       
2089        Need to add Spatial Varying Capability
2090        (This module came from copying and amending the Inflow Code)
2091   
2092    Rainfall(rain)
2093
2094    domain   
2095    rain [mm/s]:  Total rain rate over the specified domain. 
2096                  NOTE: Raingauge Data needs to reflect the time step.
2097                  IE: if Gauge is mm read at a time step, then the input
2098                  here is as mm/(timeStep) so 10mm in 5minutes becomes
2099                  10/(5x60) = 0.0333mm/s.
2100       
2101       
2102                  This parameter can be either a constant or a
2103                  function of time. Positive values indicate inflow,
2104                  negative values indicate outflow.
2105                  (and be used for Infiltration - Write Seperate Module)
2106                  The specified flow will be divided by the area of
2107                  the inflow region and then applied to update the
2108                  stage quantity.
2109
2110    polygon: Specifies a polygon to restrict the rainfall.
2111   
2112    Examples
2113    How to put them in a run File...
2114       
2115    #------------------------------------------------------------------------
2116    # Setup specialised forcing terms
2117    #------------------------------------------------------------------------
2118    # This is the new element implemented by Ole and Rudy to allow direct
2119    # input of Rainfall in mm/s
2120
2121    catchmentrainfall = Rainfall(rain=file_function('Q100_2hr_Rain.tms')) 
2122                        # Note need path to File in String.
2123                        # Else assumed in same directory
2124
2125    domain.forcing_terms.append(catchmentrainfall)
2126    """
2127
2128   
2129    def __init__(self,
2130                 domain,
2131                 rate=0.0,
2132                 center=None, radius=None,
2133                 polygon=None,
2134                 default_rate=None,                 
2135                 verbose=False):
2136
2137        # Converting mm/s to m/s to apply in ANUGA)
2138        if callable(rate):
2139            rain = lambda t: rate(t)/1000.0
2140        else:
2141            rain = rate/1000.0
2142
2143        if default_rate is not None:   
2144            if callable(default_rate):
2145                default_rain = lambda t: default_rate(t)/1000.0
2146            else:
2147                default_rain = default_rate/1000.0
2148        else:
2149            default_rain = None
2150
2151
2152           
2153           
2154        General_forcing.__init__(self,
2155                                 domain,
2156                                 'stage',
2157                                 rate=rain,
2158                                 center=center, radius=radius,
2159                                 polygon=polygon,
2160                                 default_rate=default_rain,
2161                                 verbose=verbose)
2162
2163       
2164
2165
2166
2167
2168class Inflow(General_forcing):
2169    """Class Inflow - general 'rain and drain' forcing term.
2170   
2171    Useful for implementing flows in and out of the domain.
2172   
2173    Inflow(flow, center, radius, polygon)
2174
2175    domain
2176    rate [m^3/s]: Total flow rate over the specified area. 
2177                  This parameter can be either a constant or a
2178                  function of time. Positive values indicate inflow,
2179                  negative values indicate outflow.
2180                  The specified flow will be divided by the area of
2181                  the inflow region and then applied to update stage.     
2182    center [m]: Coordinates at center of flow point
2183    radius [m]: Size of circular area
2184    polygon:    Arbitrary polygon.
2185
2186    Either center, radius or polygon must be specified
2187   
2188    Examples
2189
2190    # Constant drain at 0.003 m^3/s.
2191    # The outflow area is 0.07**2*pi=0.0154 m^2
2192    # This corresponds to a rate of change of 0.003/0.0154 = 0.2 m/s
2193    #                                     
2194    Inflow((0.7, 0.4), 0.07, -0.003)
2195
2196
2197    # Tap turning up to a maximum inflow of 0.0142 m^3/s.
2198    # The inflow area is 0.03**2*pi = 0.00283 m^2
2199    # This corresponds to a rate of change of 0.0142/0.00283 = 5 m/s     
2200    # over the specified area
2201    Inflow((0.5, 0.5), 0.03, lambda t: min(0.01*t, 0.0142))
2202
2203
2204    #------------------------------------------------------------------------
2205    # Setup specialised forcing terms
2206    #------------------------------------------------------------------------
2207    # This is the new element implemented by Ole to allow direct input
2208    # of Inflow in m^3/s
2209
2210    hydrograph = Inflow(center=(320, 300), radius=10,
2211                        rate=file_function('Q/QPMF_Rot_Sub13.tms'))
2212
2213    domain.forcing_terms.append(hydrograph)
2214   
2215    """
2216
2217
2218    def __init__(self,
2219                 domain,
2220                 rate=0.0,
2221                 center=None, radius=None,
2222                 polygon=None,
2223                 default_rate=None,
2224                 verbose=False):                 
2225
2226
2227        # Create object first to make area is available
2228        General_forcing.__init__(self,
2229                                 domain,
2230                                 'stage',
2231                                 rate=rate,
2232                                 center=center, radius=radius,
2233                                 polygon=polygon,
2234                                 default_rate=default_rate,
2235                                 verbose=verbose)
2236
2237    def update_rate(self, t):
2238        """Virtual method allowing local modifications by writing an
2239        overriding version in descendant
2240
2241        This one converts m^3/s to m/s which can be added directly
2242        to 'stage' in ANUGA
2243        """
2244
2245        if callable(self.rate):
2246            _rate = self.rate(t)/self.exchange_area
2247        else:
2248            _rate = self.rate/self.exchange_area
2249
2250        return _rate
2251
2252
2253
2254
2255#------------------
2256# Initialise module
2257#------------------
2258
2259
2260from anuga.utilities import compile
2261if compile.can_use_C_extension('shallow_water_ext.c'):
2262    # Underlying C implementations can be accessed
2263
2264    from shallow_water_ext import rotate, assign_windfield_values
2265else:
2266    msg = 'C implementations could not be accessed by %s.\n ' %__file__
2267    msg += 'Make sure compile_all.py has been run as described in '
2268    msg += 'the ANUGA installation guide.'
2269    raise Exception, msg
2270
2271
2272# Optimisation with psyco
2273from anuga.config import use_psyco
2274if use_psyco:
2275    try:
2276        import psyco
2277    except:
2278        import os
2279        if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
2280            pass
2281            #Psyco isn't supported on 64 bit systems, but it doesn't matter
2282        else:
2283            msg = 'WARNING: psyco (speedup) could not import'+\
2284                  ', you may want to consider installing it'
2285            print msg
2286    else:
2287        psyco.bind(Domain.distribute_to_vertices_and_edges)
2288        psyco.bind(Domain.compute_fluxes)
2289
2290if __name__ == "__main__":
2291    pass
2292
2293
Note: See TracBrowser for help on using the repository browser.