source: anuga_core/source_numpy_conversion/anuga/abstract_2d_finite_volumes/domain.py @ 5944

Last change on this file since 5944 was 5944, checked in by rwilson, 15 years ago

More NumPy? changes.

File size: 56.5 KB
Line 
1"""Class Domain - 2D triangular domains for finite-volume computations of
2   conservation laws.
3
4
5   Copyright 2004
6   Ole Nielsen, Stephen Roberts, Duncan Gray
7   Geoscience Australia
8"""
9
10import numpy
11
12from anuga.config import epsilon
13from anuga.config import beta_euler, beta_rk2
14
15from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
16from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
17     import Boundary
18from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
19     import File_boundary
20from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
21     import Dirichlet_boundary
22from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
23     import Time_boundary
24from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
25     import Transmissive_boundary
26
27from anuga.abstract_2d_finite_volumes.pmesh2domain import pmesh_to_domain
28from anuga.abstract_2d_finite_volumes.region\
29     import Set_region as region_set_region
30
31from anuga.utilities.polygon import inside_polygon
32from anuga.abstract_2d_finite_volumes.util import get_textual_float
33
34import types
35from time import time as walltime
36
37
38
39class Domain(Mesh):
40
41
42    def __init__(self,
43                 source=None,
44                 triangles=None,
45                 boundary=None,
46                 conserved_quantities=None,
47                 other_quantities=None,
48                 tagged_elements=None,
49                 geo_reference=None,
50                 use_inscribed_circle=False,
51                 mesh_filename=None,
52                 use_cache=False,
53                 verbose=False,
54                 full_send_dict=None,
55                 ghost_recv_dict=None,
56                 processor=0,
57                 numproc=1,
58                 number_of_full_nodes=None,
59                 number_of_full_triangles=None):
60
61
62        """Instantiate generic computational Domain.
63
64        Input:
65          source:    Either a mesh filename or coordinates of mesh vertices.
66                     If it is a filename values specified for triangles will
67                     be overridden.
68          triangles: Mesh connectivity (see mesh.py for more information)
69          boundary:  See mesh.py for more information
70
71          conserved_quantities: List of quantity names entering the
72                                conservation equations
73          other_quantities:     List of other quantity names
74
75          tagged_elements:
76          ...
77
78
79        """
80
81        # Determine whether source is a mesh filename or coordinates
82        if type(source) == types.StringType:
83            mesh_filename = source
84        else:
85            coordinates = source
86
87
88        # In case a filename has been specified, extract content
89        if mesh_filename is not None:
90            coordinates, triangles, boundary, vertex_quantity_dict, \
91                         tagged_elements, geo_reference = \
92                         pmesh_to_domain(file_name=mesh_filename,
93                                         use_cache=use_cache,
94                                         verbose=verbose)
95
96
97        # Initialise underlying mesh structure
98        Mesh.__init__(self, coordinates, triangles,
99                      boundary=boundary,
100                      tagged_elements=tagged_elements,
101                      geo_reference=geo_reference,
102                      use_inscribed_circle=use_inscribed_circle,
103                      number_of_full_nodes=number_of_full_nodes,
104                      number_of_full_triangles=number_of_full_triangles,
105                      verbose=verbose)
106
107        if verbose: print 'Initialising Domain'
108        from quantity import Quantity
109
110        # List of quantity names entering
111        # the conservation equations
112        if conserved_quantities is None:
113            self.conserved_quantities = []
114        else:
115            self.conserved_quantities = conserved_quantities
116
117        # List of other quantity names
118        if other_quantities is None:
119            self.other_quantities = []
120        else:
121            self.other_quantities = other_quantities
122
123
124        #Build dictionary of Quantity instances keyed by quantity names
125        self.quantities = {}
126
127        #FIXME: remove later - maybe OK, though....
128        for name in self.conserved_quantities:
129            self.quantities[name] = Quantity(self)
130        for name in self.other_quantities:
131            self.quantities[name] = Quantity(self)
132
133        #Create an empty list for explicit forcing terms
134        self.forcing_terms = []
135
136        #Setup the ghost cell communication
137        if full_send_dict is None:
138            self.full_send_dict = {}
139        else:
140            self.full_send_dict  = full_send_dict
141
142        # List of other quantity names
143        if ghost_recv_dict is None:
144            self.ghost_recv_dict = {}
145        else:
146            self.ghost_recv_dict = ghost_recv_dict
147
148        self.processor = processor
149        self.numproc = numproc
150
151
152        # Setup Communication Buffers
153        if verbose: print 'Domain: Set up communication buffers (parallel)'
154        self.nsys = len(self.conserved_quantities)
155        for key in self.full_send_dict:
156            buffer_shape = self.full_send_dict[key][0].shape[0]
157            self.full_send_dict[key].append(numpy.zeros( (buffer_shape,self.nsys), numpy.float))
158
159
160        for key in self.ghost_recv_dict:
161            buffer_shape = self.ghost_recv_dict[key][0].shape[0]
162            self.ghost_recv_dict[key].append(numpy.zeros( (buffer_shape,self.nsys), numpy.float))
163
164
165        # Setup cell full flag
166        # =1 for full
167        # =0 for ghost
168        N = len(self) #number_of_elements
169        self.number_of_elements = N
170        self.tri_full_flag = numpy.ones(N, numpy.int)
171        for i in self.ghost_recv_dict.keys():
172            for id in self.ghost_recv_dict[i][0]:
173                self.tri_full_flag[id] = 0
174
175        # Test the assumption that all full triangles are store before
176        # the ghost triangles.
177        if not numpy.allclose(self.tri_full_flag[:self.number_of_full_nodes],1):
178            print 'WARNING:  Not all full triangles are store before ghost triangles'
179                       
180
181        # Defaults
182        from anuga.config import max_smallsteps, beta_w, epsilon
183        from anuga.config import CFL
184        from anuga.config import timestepping_method
185        from anuga.config import protect_against_isolated_degenerate_timesteps
186        self.beta_w = beta_w
187        self.epsilon = epsilon
188        self.protect_against_isolated_degenerate_timesteps = protect_against_isolated_degenerate_timesteps
189       
190       
191        # Maybe get rid of order altogether and use beta_w
192        # FIXME (Ole): In any case, this should appear in the config file - not here
193        self.set_default_order(1)
194
195        self.smallsteps = 0
196        self.max_smallsteps = max_smallsteps
197        self.number_of_steps = 0
198        self.number_of_first_order_steps = 0
199        self.CFL = CFL
200        self.set_timestepping_method(timestepping_method)
201        self.set_beta(beta_w)
202        self.boundary_map = None  # Will be populated by set_boundary       
203       
204
205        # Model time
206        self.time = 0.0
207        self.finaltime = None
208        self.min_timestep = self.max_timestep = 0.0
209        self.starttime = 0 # Physical starttime if any
210                           # (0 is 1 Jan 1970 00:00:00)
211        self.timestep = 0.0
212        self.flux_timestep = 0.0
213
214        self.last_walltime = walltime()
215       
216        # Monitoring
217        self.quantities_to_be_monitored = None
218        self.monitor_polygon = None
219        self.monitor_time_interval = None               
220        self.monitor_indices = None
221
222        # Checkpointing and storage
223        from anuga.config import default_datadir
224        self.datadir = default_datadir
225        self.simulation_name = 'domain'
226        self.checkpoint = False
227
228        # To avoid calculating the flux across each edge twice,
229        # keep an integer (boolean) array, to be used during the flux
230        # calculation
231        N = len(self) # Number_of_triangles
232        self.already_computed_flux = numpy.zeros((N, 3), numpy.int)
233
234        # Storage for maximal speeds computed for each triangle by
235        # compute_fluxes
236        # This is used for diagnostics only (reset at every yieldstep)
237        self.max_speed = numpy.zeros(N, numpy.float)
238
239        if mesh_filename is not None:
240            # If the mesh file passed any quantity values
241            # , initialise with these values.
242            if verbose: print 'Domain: Initialising quantity values'
243            self.set_quantity_vertices_dict(vertex_quantity_dict)
244
245
246        if verbose: print 'Domain: Done'
247
248
249
250
251
252
253    #---------------------------   
254    # Public interface to Domain
255    #---------------------------       
256    def get_conserved_quantities(self, vol_id, vertex=None, edge=None):
257        """Get conserved quantities at volume vol_id
258
259        If vertex is specified use it as index for vertex values
260        If edge is specified use it as index for edge values
261        If neither are specified use centroid values
262        If both are specified an exeception is raised
263
264        Return value: Vector of length == number_of_conserved quantities
265
266        """
267
268        if not (vertex is None or edge is None):
269            msg = 'Values for both vertex and edge was specified.'
270            msg += 'Only one (or none) is allowed.'
271            raise msg
272
273        q = numpy.zeros( len(self.conserved_quantities), numpy.float)
274
275        for i, name in enumerate(self.conserved_quantities):
276            Q = self.quantities[name]
277            if vertex is not None:
278                q[i] = Q.vertex_values[vol_id, vertex]
279            elif edge is not None:
280                q[i] = Q.edge_values[vol_id, edge]
281            else:
282                q[i] = Q.centroid_values[vol_id]
283
284        return q
285
286    def set_time(self, time=0.0):
287        """Set the model time (seconds)"""
288        # FIXME: this is setting the relative time
289        # Note that get_time and set_time are now not symmetric
290
291        self.time = time
292
293    def get_time(self):
294        """Get the absolute model time (seconds)"""
295
296        return self.time + self.starttime
297
298    def set_beta(self,beta):
299        """Set default beta for limiting
300        """
301
302        self.beta = beta
303        for name in self.quantities:
304            #print 'setting beta for quantity ',name
305            Q = self.quantities[name]
306            Q.set_beta(beta)
307
308    def get_beta(self):
309        """Get default beta for limiting
310        """
311
312        return self.beta
313
314    def set_default_order(self, n):
315        """Set default (spatial) order to either 1 or 2
316        """
317
318        msg = 'Default order must be either 1 or 2. I got %s' %n
319        assert n in [1,2], msg
320
321        self.default_order = n
322        self._order_ = self.default_order
323
324        if self.default_order == 1:
325            pass
326            #self.set_timestepping_method('euler')
327            #self.set_all_limiters(beta_euler)
328
329        if self.default_order == 2:
330            pass
331            #self.set_timestepping_method('rk2')
332            #self.set_all_limiters(beta_rk2)
333       
334
335    def set_quantity_vertices_dict(self, quantity_dict):
336        """Set values for named quantities.
337        The index is the quantity
338
339        name: Name of quantity
340        X: Compatible list, Numeric array, const or function (see below)
341
342        The values will be stored in elements following their
343        internal ordering.
344
345        """
346       
347        # FIXME: Could we name this a bit more intuitively
348        # E.g. set_quantities_from_dictionary
349        for key in quantity_dict.keys():
350            self.set_quantity(key, quantity_dict[key], location='vertices')
351
352
353    def set_quantity(self, name, *args, **kwargs):
354        """Set values for named quantity
355
356
357        One keyword argument is documented here:
358        expression = None, # Arbitrary expression
359
360        expression:
361          Arbitrary expression involving quantity names
362
363        See Quantity.set_values for further documentation.
364        """
365
366        # Do the expression stuff
367        if kwargs.has_key('expression'):
368            expression = kwargs['expression']
369            del kwargs['expression']
370
371            Q = self.create_quantity_from_expression(expression)
372            kwargs['quantity'] = Q
373
374        # Assign values
375        self.quantities[name].set_values(*args, **kwargs)
376
377
378    def get_quantity_names(self):
379        """Get a list of all the quantity names that this domain is aware of.
380        Any value in the result should be a valid input to get_quantity.
381        """
382        return self.quantities.keys()
383
384    def get_quantity(self, name, location='vertices', indices = None):
385        """Get pointer to quantity object.
386
387        name: Name of quantity
388
389        See methods inside the quantity object for more options
390
391        FIXME: clean input args
392        """
393
394        return self.quantities[name] #.get_values( location, indices = indices)
395
396
397
398    def create_quantity_from_expression(self, expression):
399        """Create new quantity from other quantities using arbitrary expression
400
401        Combine existing quantities in domain using expression and return
402        result as a new quantity.
403
404        Note, the new quantity could e.g. be used in set_quantity
405
406        Valid expressions are limited to operators defined in class Quantity
407
408        Examples creating derived quantities:
409
410            Depth = domain.create_quantity_from_expression('stage-elevation')
411
412            exp = '(xmomentum*xmomentum + ymomentum*ymomentum)**0.5'       
413            Absolute_momentum = domain.create_quantity_from_expression(exp)
414
415        """
416
417        from anuga.abstract_2d_finite_volumes.util import\
418             apply_expression_to_dictionary
419       
420        return apply_expression_to_dictionary(expression, self.quantities)
421
422
423
424    def set_boundary(self, boundary_map):
425        """Associate boundary objects with tagged boundary segments.
426
427        Input boundary_map is a dictionary of boundary objects keyed
428        by symbolic tags to matched against tags in the internal dictionary
429        self.boundary.
430
431        As result one pointer to a boundary object is stored for each vertex
432        in the list self.boundary_objects.
433        More entries may point to the same boundary object
434
435        Schematically the mapping is from two dictionaries to one list
436        where the index is used as pointer to the boundary_values arrays
437        within each quantity.
438
439        self.boundary:          (vol_id, edge_id): tag
440        boundary_map (input):   tag: boundary_object
441        ----------------------------------------------
442        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
443
444
445        Pre-condition:
446          self.boundary has been built.
447
448        Post-condition:
449          self.boundary_objects is built
450
451        If a tag from the domain doesn't appear in the input dictionary an
452        exception is raised.
453        However, if a tag is not used to the domain, no error is thrown.
454        FIXME: This would lead to implementation of a
455        default boundary condition
456
457        Note: If a segment is listed in the boundary dictionary and if it is
458        not None, it *will* become a boundary -
459        even if there is a neighbouring triangle.
460        This would be the case for internal boundaries
461
462        Boundary objects that are None will be skipped.
463
464        If a boundary_map has already been set
465        (i.e. set_boundary has been called before), the old boundary map
466        will be updated with new values. The new map need not define all
467        boundary tags, and can thus change only those that are needed.
468
469        FIXME: If set_boundary is called multiple times and if Boundary
470        object is changed into None, the neighbour structure will not be
471        restored!!!
472
473
474        """
475
476        if self.boundary_map is None:
477            # This the first call to set_boundary. Store
478            # map for later updates and for use with boundary_stats.
479            self.boundary_map = boundary_map
480        else:   
481            # This is a modification of an already existing map
482            # Update map an proceed normally
483
484            for key in boundary_map.keys():
485                self.boundary_map[key] = boundary_map[key]
486               
487       
488        # FIXME (Ole): Try to remove the sorting and fix test_mesh.py
489        x = self.boundary.keys()
490        x.sort()
491
492        # Loop through edges that lie on the boundary and associate them with
493        # callable boundary objects depending on their tags
494        self.boundary_objects = []       
495        for k, (vol_id, edge_id) in enumerate(x):
496            tag = self.boundary[ (vol_id, edge_id) ]
497
498            if self.boundary_map.has_key(tag):
499                B = self.boundary_map[tag]  # Get callable boundary object
500
501                if B is not None:
502                    self.boundary_objects.append( ((vol_id, edge_id), B) )
503                    self.neighbours[vol_id, edge_id] = \
504                                            -len(self.boundary_objects)
505                else:
506                    pass
507                    #FIXME: Check and perhaps fix neighbour structure
508
509
510            else:
511                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
512                msg += 'bound to a boundary object.\n'
513                msg += 'All boundary tags defined in domain must appear '
514                msg += 'in set_boundary.\n'
515                msg += 'The tags are: %s' %self.get_boundary_tags()
516                raise msg
517
518
519    def set_region(self, *args, **kwargs):
520        """
521        This method is used to set quantities based on a regional tag.
522       
523        It is most often called with the following parameters;
524        (self, tag, quantity, X, location='vertices')
525        tag: the name of the regional tag used to specify the region
526        quantity: Name of quantity to change
527        X: const or function - how the quantity is changed
528        location: Where values are to be stored.
529            Permissible options are: vertices, centroid and unique vertices
530
531        A callable region class or a list of callable region classes
532        can also be passed into this function.
533        """
534
535        if len(args) == 1:
536            self._set_region(*args, **kwargs)
537        else:
538            # Assume it is arguments for the region.set_region function
539            func = region_set_region(*args, **kwargs)
540            self._set_region(func)
541           
542       
543    def _set_region(self, functions):
544        # The order of functions in the list is used.
545        if type(functions) not in [types.ListType,types.TupleType]:
546            functions = [functions]
547        for function in functions:
548            for tag in self.tagged_elements.keys():
549                function(tag, self.tagged_elements[tag], self)
550
551
552
553
554    def set_quantities_to_be_monitored(self, q,
555                                       polygon=None,
556                                       time_interval=None):
557        """Specify which quantities will be monitored for extrema.
558
559        q must be either:
560          - the name of a quantity or a derived quantity such as 'stage-elevation'
561          - a list of quantity names
562          - None
563
564        In the two first cases, the named quantities will be monitored at
565        each internal timestep
566       
567        If q is None, monitoring will be switched off altogether.
568
569        polygon (if specified) will restrict monitoring to triangles inside polygon.
570        If omitted all triangles will be included.
571
572        time_interval (if specified) will restrict monitoring to time steps in
573        that interval. If omitted all timesteps will be included.
574        """
575
576        from anuga.abstract_2d_finite_volumes.util import\
577             apply_expression_to_dictionary       
578
579        if q is None:
580            self.quantities_to_be_monitored = None
581            self.monitor_polygon = None
582            self.monitor_time_interval = None
583            self.monitor_indices = None           
584            return
585
586        if isinstance(q, basestring):
587            q = [q] # Turn argument into a list
588
589        # Check correcness and initialise
590        self.quantities_to_be_monitored = {}
591        for quantity_name in q:
592            msg = 'Quantity %s is not a valid conserved quantity'\
593                  %quantity_name
594           
595
596            if not quantity_name in self.quantities:
597                # See if this expression is valid
598                apply_expression_to_dictionary(quantity_name, self.quantities)
599
600            # Initialise extrema information
601            info_block = {'min': None,          # Min value
602                          'max': None,          # Max value
603                          'min_location': None, # Argmin (x, y)
604                          'max_location': None, # Argmax (x, y)
605                          'min_time': None,     # Argmin (t)
606                          'max_time': None}     # Argmax (t)
607           
608            self.quantities_to_be_monitored[quantity_name] = info_block
609
610           
611
612        if polygon is not None:
613            # Check input
614            if isinstance(polygon, basestring):
615
616                # Check if multiple quantities were accidentally
617                # given as separate argument rather than a list.
618                msg = 'Multiple quantities must be specified in a list. '
619                msg += 'Not as multiple arguments. '
620                msg += 'I got "%s" as a second argument' %polygon
621               
622                if polygon in self.quantities:
623                    raise Exception, msg
624               
625                try:
626                    apply_expression_to_dictionary(polygon, self.quantities)
627                except:
628                    # At least polygon wasn't an expression involving quantitites
629                    pass
630                else:
631                    raise Exception, msg
632
633                # In any case, we don't allow polygon to be a string
634                msg = 'argument "polygon" must not be a string: '
635                msg += 'I got polygon=\'%s\' ' %polygon
636                raise Exception, msg
637
638
639            # Get indices for centroids that are inside polygon
640            points = self.get_centroid_coordinates(absolute=True)
641            self.monitor_indices = inside_polygon(points, polygon)
642           
643
644        if time_interval is not None:
645            assert len(time_interval) == 2
646
647       
648        self.monitor_polygon = polygon
649        self.monitor_time_interval = time_interval       
650       
651
652    #--------------------------   
653    # Miscellaneous diagnostics
654    #--------------------------       
655    def check_integrity(self):
656        Mesh.check_integrity(self)
657
658        for quantity in self.conserved_quantities:
659            msg = 'Conserved quantities must be a subset of all quantities'
660            assert quantity in self.quantities, msg
661
662        ##assert hasattr(self, 'boundary_objects')
663           
664
665    def write_time(self, track_speeds=False):
666        print self.timestepping_statistics(track_speeds)
667
668
669    def timestepping_statistics(self,
670                                track_speeds=False,
671                                triangle_id=None):
672        """Return string with time stepping statistics for printing or logging
673
674        Optional boolean keyword track_speeds decides whether to report
675        location of smallest timestep as well as a histogram and percentile
676        report.
677
678        Optional keyword triangle_id can be used to specify a particular
679        triangle rather than the one with the largest speed.
680        """
681
682        from anuga.utilities.numerical_tools import histogram, create_bins
683       
684
685        # qwidth determines the text field used for quantities
686        qwidth = self.qwidth = 12
687
688
689        msg = ''
690        #if self.min_timestep == self.max_timestep:
691        #    msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
692        #           %(self.time, self.min_timestep, self.number_of_steps,
693        #             self.number_of_first_order_steps)
694        #elif self.min_timestep > self.max_timestep:
695        #    msg += 'Time = %.4f, steps=%d (%d)'\
696        #           %(self.time, self.number_of_steps,
697        #             self.number_of_first_order_steps)
698        #else:
699        #    msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
700        #           %(self.time, self.min_timestep,
701        #             self.max_timestep, self.number_of_steps,
702        #             self.number_of_first_order_steps)
703
704        model_time = self.get_time()
705        if self.min_timestep == self.max_timestep:
706            msg += 'Time = %.4f, delta t = %.8f, steps=%d'\
707                   %(model_time, self.min_timestep, self.number_of_steps)
708        elif self.min_timestep > self.max_timestep:
709            msg += 'Time = %.4f, steps=%d'\
710                   %(model_time, self.number_of_steps)
711        else:
712            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d'\
713                   %(model_time, self.min_timestep,
714                     self.max_timestep, self.number_of_steps)
715                                         
716        msg += ' (%ds)' %(walltime() - self.last_walltime)   
717        self.last_walltime = walltime()           
718       
719        if track_speeds is True:
720            msg += '\n'
721
722
723            # Setup 10 bins for speed histogram
724            bins = create_bins(self.max_speed, 10)
725            hist = histogram(self.max_speed, bins)
726
727            msg += '------------------------------------------------\n'
728            msg += '  Speeds in [%f, %f]\n' %(min(self.max_speed),
729                                              max(self.max_speed))
730            msg += '  Histogram:\n'
731
732            hi = bins[0]
733            for i, count in enumerate(hist):
734                lo = hi
735                if i+1 < len(bins):
736                    # Open upper interval               
737                    hi = bins[i+1]
738                    msg += '    [%f, %f[: %d\n' %(lo, hi, count)               
739                else:
740                    # Closed upper interval
741                    hi = max(self.max_speed)
742                    msg += '    [%f, %f]: %d\n' %(lo, hi, count)
743
744
745            N = len(self.max_speed)
746            if N > 10:
747                msg += '  Percentiles (10%):\n'
748                speed = self.max_speed.tolist()
749                speed.sort()
750
751                k = 0
752                lower = min(speed)
753                for i, a in enumerate(speed):       
754                    if i % (N/10) == 0 and i != 0:
755                        # For every 10% of the sorted speeds
756                        msg += '    %d speeds in [%f, %f]\n' %(i-k, lower, a)
757                        lower = a
758                        k = i
759                       
760                msg += '    %d speeds in [%f, %f]\n'\
761                       %(N-k, lower, max(speed))                   
762               
763                     
764
765               
766           
767            # Find index of largest computed flux speed
768            if triangle_id is None:
769                k = self.k = numpy.argmax(self.max_speed)
770            else:
771                errmsg = 'Triangle_id %d does not exist in mesh: %s' %(triangle_id,
772                                                                    str(self))
773                assert 0 <= triangle_id < len(self), errmsg
774                k = self.k = triangle_id
775           
776
777            x, y = self.get_centroid_coordinates()[k]
778            radius = self.get_radii()[k]
779            area = self.get_areas()[k]     
780            max_speed = self.max_speed[k]           
781
782            msg += '  Triangle #%d with centroid (%.4f, %.4f), ' %(k, x, y)
783            msg += 'area = %.4f and radius = %.4f ' %(area, radius)
784            if triangle_id is None:
785                msg += 'had the largest computed speed: %.6f m/s ' %(max_speed)
786            else:
787                msg += 'had computed speed: %.6f m/s ' %(max_speed)
788               
789            if max_speed > 0.0:
790                msg += '(timestep=%.6f)\n' %(radius/max_speed)
791            else:
792                msg += '(timestep=%.6f)\n' %(0)     
793           
794            # Report all quantity values at vertices, edges and centroid
795            msg += '    Quantity'
796            msg += '------------\n'
797            for name in self.quantities:
798                q = self.quantities[name]
799
800                V = q.get_values(location='vertices', indices=[k])[0]
801                E = q.get_values(location='edges', indices=[k])[0]
802                C = q.get_values(location='centroids', indices=[k])                 
803               
804                s  = '    %s: vertex_values =  %.4f,\t %.4f,\t %.4f\n'\
805                     %(name.ljust(qwidth), V[0], V[1], V[2])
806
807                s += '    %s: edge_values =    %.4f,\t %.4f,\t %.4f\n'\
808                     %(name.ljust(qwidth), E[0], E[1], E[2])
809
810                s += '    %s: centroid_value = %.4f\n'\
811                     %(name.ljust(qwidth), C[0])                               
812
813                msg += s
814
815        return msg
816
817
818    def write_boundary_statistics(self, quantities = None, tags = None):
819        print self.boundary_statistics(quantities, tags)
820
821    def boundary_statistics(self, quantities = None, tags = None):
822        """Output statistics about boundary forcing at each timestep
823
824
825        Input:
826          quantities: either None, a string or a list of strings naming the
827                      quantities to be reported
828          tags:       either None, a string or a list of strings naming the
829                      tags to be reported
830
831
832        Example output:
833        Tag 'wall':
834            stage in [2, 5.5]
835            xmomentum in []
836            ymomentum in []
837        Tag 'ocean'
838
839
840        If quantities are specified only report on those. Otherwise take all
841        conserved quantities.
842        If tags are specified only report on those, otherwise take all tags.
843
844        """
845
846        # Input checks
847        import types, string
848
849        if quantities is None:
850            quantities = self.conserved_quantities
851        elif type(quantities) == types.StringType:
852            quantities = [quantities] #Turn it into a list
853
854        msg = 'Keyword argument quantities must be either None, '
855        msg += 'string or list. I got %s' %str(quantities)
856        assert type(quantities) == types.ListType, msg
857
858
859        if tags is None:
860            tags = self.get_boundary_tags()
861        elif type(tags) == types.StringType:
862            tags = [tags] #Turn it into a list
863
864        msg = 'Keyword argument tags must be either None, '
865        msg += 'string or list. I got %s' %str(tags)
866        assert type(tags) == types.ListType, msg
867
868        # Determine width of longest quantity name (for cosmetic purposes)
869        maxwidth = 0
870        for name in quantities:
871            w = len(name)
872            if w > maxwidth:
873                maxwidth = w
874
875        # Output statistics
876        msg = 'Boundary values at time %.4f:\n' %self.time
877        for tag in tags:
878            msg += '    %s:\n' %tag
879
880            for name in quantities:
881                q = self.quantities[name]
882
883                # Find range of boundary values for tag and q
884                maxval = minval = None
885                for i, ((vol_id, edge_id), B) in\
886                        enumerate(self.boundary_objects):
887                    if self.boundary[(vol_id, edge_id)] == tag:
888                        v = q.boundary_values[i]
889                        if minval is None or v < minval: minval = v
890                        if maxval is None or v > maxval: maxval = v
891
892                if minval is None or maxval is None:
893                    msg += '        Sorry no information available about' +\
894                           ' tag %s and quantity %s\n' %(tag, name)
895                else:
896                    msg += '        %s in [%12.8f, %12.8f]\n'\
897                           %(string.ljust(name, maxwidth), minval, maxval)
898
899
900        return msg
901
902
903    def update_extrema(self):
904        """Update extrema if requested by set_quantities_to_be_monitored.
905        This data is used for reporting e.g. by running
906        print domain.quantity_statistics()
907        and may also stored in output files (see data_manager in shallow_water)
908        """
909
910        # Define a tolerance for extremum computations
911        epsilon = 1.0e-6 # Import 'single_precision' from config
912       
913        if self.quantities_to_be_monitored is None:
914            return
915
916        # Observe time interval restriction if any
917        if self.monitor_time_interval is not None and\
918               (self.time < self.monitor_time_interval[0] or\
919               self.time > self.monitor_time_interval[1]):
920            return
921
922        # Update extrema for each specified quantity subject to
923        # polygon restriction (via monitor_indices).
924        for quantity_name in self.quantities_to_be_monitored:
925
926            if quantity_name in self.quantities:
927                Q = self.get_quantity(quantity_name)
928            else:
929                Q = self.create_quantity_from_expression(quantity_name)
930
931            info_block = self.quantities_to_be_monitored[quantity_name]
932
933            # Update maximum
934            # (n > None is always True, but we check explicitly because
935            # of the epsilon)
936            maxval = Q.get_maximum_value(self.monitor_indices)
937            if info_block['max'] is None or\
938                   maxval > info_block['max'] + epsilon:
939                info_block['max'] = maxval
940                maxloc = Q.get_maximum_location()
941                info_block['max_location'] = maxloc
942                info_block['max_time'] = self.time
943
944
945            # Update minimum
946            minval = Q.get_minimum_value(self.monitor_indices)
947            if info_block['min'] is None or\
948                   minval < info_block['min'] - epsilon:
949                info_block['min'] = minval               
950                minloc = Q.get_minimum_location()
951                info_block['min_location'] = minloc
952                info_block['min_time'] = self.time               
953       
954
955
956    def quantity_statistics(self, precision = '%.4f'):
957        """Return string with statistics about quantities for
958        printing or logging
959
960        Quantities reported are specified through method
961
962           set_quantities_to_be_monitored
963           
964        """
965
966        maxlen = 128 # Max length of polygon string representation
967
968        # Output statistics
969        msg = 'Monitored quantities at time %.4f:\n' %self.time
970        if self.monitor_polygon is not None:
971            p_str = str(self.monitor_polygon)
972            msg += '- Restricted by polygon: %s' %p_str[:maxlen]
973            if len(p_str) >= maxlen:
974                msg += '...\n'
975            else:
976                msg += '\n'
977
978
979        if self.monitor_time_interval is not None:
980            msg += '- Restricted by time interval: %s\n'\
981                   %str(self.monitor_time_interval)
982            time_interval_start = self.monitor_time_interval[0]
983        else:
984            time_interval_start = 0.0
985
986           
987        for quantity_name, info in self.quantities_to_be_monitored.items():
988            msg += '    %s:\n' %quantity_name
989
990            msg += '      values since time = %.2f in [%s, %s]\n'\
991                   %(time_interval_start,
992                     get_textual_float(info['min'], precision),
993                     get_textual_float(info['max'], precision))       
994                     
995            msg += '      minimum attained at time = %s, location = %s\n'\
996                   %(get_textual_float(info['min_time'], precision),
997                     get_textual_float(info['min_location'], precision))
998           
999
1000            msg += '      maximum attained at time = %s, location = %s\n'\
1001                   %(get_textual_float(info['max_time'], precision),
1002                     get_textual_float(info['max_location'], precision))
1003
1004
1005        return msg
1006
1007    def get_timestepping_method(self):
1008        return self.timestepping_method
1009
1010    def set_timestepping_method(self,timestepping_method):
1011       
1012        if timestepping_method in ['euler', 'rk2', 'rk3']:
1013            self.timestepping_method = timestepping_method
1014            return
1015
1016        msg = '%s is an incorrect timestepping type'% timestepping_method
1017        raise Exception, msg
1018
1019    def get_name(self):
1020        return self.simulation_name
1021
1022    def set_name(self, name):
1023        """Assign a name to this simulation.
1024        This will be used to identify the output sww file.
1025
1026        """
1027        if name.endswith('.sww'):
1028            name = name[:-4]
1029           
1030        self.simulation_name = name
1031
1032    def get_datadir(self):
1033        return self.datadir
1034
1035    def set_datadir(self, name):
1036        self.datadir = name
1037
1038    def get_starttime(self):
1039        return self.starttime
1040
1041    def set_starttime(self, time):
1042        self.starttime = float(time)       
1043
1044
1045
1046    #--------------------------
1047    # Main components of evolve
1048    #--------------------------   
1049
1050    def evolve(self,
1051               yieldstep = None,
1052               finaltime = None,
1053               duration = None,
1054               skip_initial_step = False):
1055        """Evolve model through time starting from self.starttime.
1056
1057
1058        yieldstep: Interval between yields where results are stored,
1059                   statistics written and domain inspected or
1060                   possibly modified. If omitted the internal predefined
1061                   max timestep is used.
1062                   Internally, smaller timesteps may be taken.
1063
1064        duration: Duration of simulation
1065
1066        finaltime: Time where simulation should end. This is currently
1067        relative time.  So it's the same as duration.
1068
1069        If both duration and finaltime are given an exception is thrown.
1070
1071
1072        skip_initial_step: Boolean flag that decides whether the first
1073        yield step is skipped or not. This is useful for example to avoid
1074        duplicate steps when multiple evolve processes are dove tailed.
1075
1076
1077        Evolve is implemented as a generator and is to be called as such, e.g.
1078
1079        for t in domain.evolve(yieldstep, finaltime):
1080            <Do something with domain and t>
1081
1082
1083        All times are given in seconds
1084
1085        """
1086
1087        from anuga.config import min_timestep, max_timestep, epsilon
1088
1089        # FIXME: Maybe lump into a larger check prior to evolving
1090        msg = 'Boundary tags must be bound to boundary objects before '
1091        msg += 'evolving system, '
1092        msg += 'e.g. using the method set_boundary.\n'
1093        msg += 'This system has the boundary tags %s '\
1094               %self.get_boundary_tags()
1095        assert hasattr(self, 'boundary_objects'), msg
1096
1097
1098        if yieldstep is None:
1099            yieldstep = max_timestep
1100        else:
1101            yieldstep = float(yieldstep)
1102
1103        self._order_ = self.default_order
1104
1105
1106        if finaltime is not None and duration is not None:
1107            # print 'F', finaltime, duration
1108            msg = 'Only one of finaltime and duration may be specified'
1109            raise msg
1110        else:
1111            if finaltime is not None:
1112                self.finaltime = float(finaltime)
1113            if duration is not None:
1114                self.finaltime = self.starttime + float(duration)
1115
1116
1117
1118        N = len(self) # Number of triangles
1119        self.yieldtime = 0.0 # Track time between 'yields'
1120
1121        # Initialise interval of timestep sizes (for reporting only)
1122        self.min_timestep = max_timestep
1123        self.max_timestep = min_timestep
1124        self.number_of_steps = 0
1125        self.number_of_first_order_steps = 0
1126
1127        # Update ghosts
1128        self.update_ghosts()
1129
1130        # Initial update of vertex and edge values
1131        self.distribute_to_vertices_and_edges()
1132
1133        # Update extrema if necessary (for reporting)
1134        self.update_extrema()
1135       
1136        # Initial update boundary values
1137        self.update_boundary()
1138
1139        # Or maybe restore from latest checkpoint
1140        if self.checkpoint is True:
1141            self.goto_latest_checkpoint()
1142
1143        if skip_initial_step is False:
1144            yield(self.time)  # Yield initial values
1145
1146        while True:
1147
1148            # Evolve One Step, using appropriate timestepping method
1149            if self.get_timestepping_method() == 'euler':
1150                self.evolve_one_euler_step(yieldstep,finaltime)
1151               
1152            elif self.get_timestepping_method() == 'rk2':
1153                self.evolve_one_rk2_step(yieldstep,finaltime)
1154
1155            elif self.get_timestepping_method() == 'rk3':
1156                self.evolve_one_rk3_step(yieldstep,finaltime)               
1157           
1158            # Update extrema if necessary (for reporting)
1159            self.update_extrema()           
1160
1161
1162            self.yieldtime += self.timestep
1163            self.number_of_steps += 1
1164            if self._order_ == 1:
1165                self.number_of_first_order_steps += 1
1166
1167            # Yield results
1168            if finaltime is not None and self.time >= finaltime-epsilon:
1169
1170                if self.time > finaltime:
1171                    # FIXME (Ole, 30 April 2006): Do we need this check?
1172                    # Probably not (Ole, 18 September 2008). Now changed to
1173                    # Exception
1174                    msg = 'WARNING (domain.py): time overshot finaltime. '
1175                    msg += 'Contact Ole.Nielsen@ga.gov.au'
1176                    raise Exception, msg
1177                   
1178
1179                # Yield final time and stop
1180                self.time = finaltime
1181                yield(self.time)
1182                break
1183
1184
1185            if self.yieldtime >= yieldstep:
1186                # Yield (intermediate) time and allow inspection of domain
1187
1188                if self.checkpoint is True:
1189                    self.store_checkpoint()
1190                    self.delete_old_checkpoints()
1191
1192                # Pass control on to outer loop for more specific actions
1193                yield(self.time)
1194
1195                # Reinitialise
1196                self.yieldtime = 0.0
1197                self.min_timestep = max_timestep
1198                self.max_timestep = min_timestep
1199                self.number_of_steps = 0
1200                self.number_of_first_order_steps = 0
1201                self.max_speed = numpy.zeros(N, numpy.float)
1202
1203    def evolve_one_euler_step(self, yieldstep, finaltime):
1204        """
1205        One Euler Time Step
1206        Q^{n+1} = E(h) Q^n
1207        """
1208
1209        # Compute fluxes across each element edge
1210        self.compute_fluxes()
1211
1212        # Update timestep to fit yieldstep and finaltime
1213        self.update_timestep(yieldstep, finaltime)
1214
1215        # Update conserved quantities
1216        self.update_conserved_quantities()
1217
1218        # Update ghosts
1219        self.update_ghosts()
1220
1221
1222        # Update time
1223        self.time += self.timestep
1224
1225        # Update vertex and edge values
1226        self.distribute_to_vertices_and_edges()
1227
1228        # Update boundary values
1229        self.update_boundary()
1230
1231
1232       
1233
1234
1235    def evolve_one_rk2_step(self, yieldstep, finaltime):
1236        """
1237        One 2nd order RK timestep
1238        Q^{n+1} = 0.5 Q^n + 0.5 E(h)^2 Q^n
1239        """
1240
1241        # Save initial initial conserved quantities values
1242        self.backup_conserved_quantities()           
1243
1244        #--------------------------------------
1245        # First euler step
1246        #--------------------------------------
1247
1248        # Compute fluxes across each element edge
1249        self.compute_fluxes()
1250
1251        # Update timestep to fit yieldstep and finaltime
1252        self.update_timestep(yieldstep, finaltime)
1253
1254        # Update conserved quantities
1255        self.update_conserved_quantities()
1256
1257        # Update ghosts
1258        self.update_ghosts()
1259
1260        # Update time
1261        self.time += self.timestep
1262
1263        # Update vertex and edge values
1264        self.distribute_to_vertices_and_edges()
1265
1266        # Update boundary values
1267        self.update_boundary()
1268
1269        #------------------------------------
1270        # Second Euler step
1271        #------------------------------------
1272           
1273        # Compute fluxes across each element edge
1274        self.compute_fluxes()
1275
1276        # Update conserved quantities
1277        self.update_conserved_quantities()
1278
1279        #------------------------------------
1280        # Combine initial and final values
1281        # of conserved quantities and cleanup
1282        #------------------------------------
1283       
1284        # Combine steps
1285        self.saxpy_conserved_quantities(0.5, 0.5)
1286 
1287        # Update ghosts
1288        self.update_ghosts()
1289
1290        # Update vertex and edge values
1291        self.distribute_to_vertices_and_edges()
1292
1293        # Update boundary values
1294        self.update_boundary()
1295
1296
1297
1298    def evolve_one_rk3_step(self, yieldstep, finaltime):
1299        """
1300        One 3rd order RK timestep
1301        Q^(1) = 3/4 Q^n + 1/4 E(h)^2 Q^n  (at time t^n + h/2)
1302        Q^{n+1} = 1/3 Q^n + 2/3 E(h) Q^(1) (at time t^{n+1})
1303        """
1304
1305        # Save initial initial conserved quantities values
1306        self.backup_conserved_quantities()           
1307
1308        initial_time = self.time
1309       
1310        #--------------------------------------
1311        # First euler step
1312        #--------------------------------------
1313
1314        # Compute fluxes across each element edge
1315        self.compute_fluxes()
1316
1317        # Update timestep to fit yieldstep and finaltime
1318        self.update_timestep(yieldstep, finaltime)
1319
1320        # Update conserved quantities
1321        self.update_conserved_quantities()
1322
1323        # Update ghosts
1324        self.update_ghosts()
1325
1326        # Update time
1327        self.time += self.timestep
1328
1329        # Update vertex and edge values
1330        self.distribute_to_vertices_and_edges()
1331
1332        # Update boundary values
1333        self.update_boundary()
1334
1335
1336        #------------------------------------
1337        # Second Euler step
1338        #------------------------------------
1339           
1340        # Compute fluxes across each element edge
1341        self.compute_fluxes()
1342
1343        # Update conserved quantities
1344        self.update_conserved_quantities()
1345
1346        #------------------------------------
1347        #Combine steps to obtain intermediate
1348        #solution at time t^n + 0.5 h
1349        #------------------------------------
1350
1351        # Combine steps
1352        self.saxpy_conserved_quantities(0.25, 0.75)
1353 
1354        # Update ghosts
1355        self.update_ghosts()
1356
1357
1358        # Set substep time
1359        self.time = initial_time + self.timestep*0.5
1360
1361        # Update vertex and edge values
1362        self.distribute_to_vertices_and_edges()
1363
1364        # Update boundary values
1365        self.update_boundary()
1366
1367
1368        #------------------------------------
1369        # Third Euler step
1370        #------------------------------------
1371           
1372        # Compute fluxes across each element edge
1373        self.compute_fluxes()
1374
1375        # Update conserved quantities
1376        self.update_conserved_quantities()
1377
1378        #------------------------------------
1379        # Combine final and initial values
1380        # and cleanup
1381        #------------------------------------
1382       
1383        # Combine steps
1384        self.saxpy_conserved_quantities(2.0/3.0, 1.0/3.0)
1385 
1386        # Update ghosts
1387        self.update_ghosts()
1388
1389        # Set new time
1390        self.time = initial_time + self.timestep       
1391
1392        # Update vertex and edge values
1393        self.distribute_to_vertices_and_edges()
1394
1395        # Update boundary values
1396        self.update_boundary()
1397
1398       
1399    def evolve_to_end(self, finaltime = 1.0):
1400        """Iterate evolve all the way to the end      """
1401
1402        for _ in self.evolve(yieldstep=None, finaltime=finaltime):
1403            pass
1404
1405
1406    def backup_conserved_quantities(self):
1407        N = len(self) # Number_of_triangles
1408
1409        # Backup conserved_quantities centroid values
1410        for name in self.conserved_quantities:
1411            Q = self.quantities[name]
1412            Q.backup_centroid_values()       
1413
1414    def saxpy_conserved_quantities(self,a,b):
1415        N = len(self) #number_of_triangles
1416
1417        # Backup conserved_quantities centroid values
1418        for name in self.conserved_quantities:
1419            Q = self.quantities[name]
1420            Q.saxpy_centroid_values(a,b)       
1421   
1422
1423    def update_boundary(self):
1424        """Go through list of boundary objects and update boundary values
1425        for all conserved quantities on boundary.
1426        It is assumed that the ordering of conserved quantities is
1427        consistent between the domain and the boundary object, i.e.
1428        the jth element of vector q must correspond to the jth conserved
1429        quantity in domain.
1430        """
1431
1432        # FIXME: Update only those that change (if that can be worked out)
1433        # FIXME: Boundary objects should not include ghost nodes.
1434        for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
1435            if B is None:
1436                print 'WARNING: Ignored boundary segment %d (None)'
1437            else:
1438                q = B.evaluate(vol_id, edge_id)
1439
1440                for j, name in enumerate(self.conserved_quantities):
1441                    Q = self.quantities[name]
1442                    Q.boundary_values[i] = q[j]
1443
1444
1445    def compute_fluxes(self):
1446        msg = 'Method compute_fluxes must be overridden by Domain subclass'
1447        raise msg
1448
1449
1450    def update_timestep(self, yieldstep, finaltime):
1451
1452        from anuga.config import min_timestep, max_timestep
1453
1454       
1455       
1456        # Protect against degenerate timesteps arising from isolated
1457        # triangles
1458        # FIXME (Steve): This should be in shallow_water as it assumes x and y
1459        # momentum
1460        if self.protect_against_isolated_degenerate_timesteps is True and \
1461               numpy.alltrue(self.max_speed > 10.0): # FIXME (Ole): Make this configurable
1462
1463            # Setup 10 bins for speed histogram
1464            from anuga.utilities.numerical_tools import histogram, create_bins
1465       
1466            bins = create_bins(self.max_speed, 10)
1467            hist = histogram(self.max_speed, bins)
1468
1469            # Look for characteristic signature
1470            if len(hist) > 1 and\
1471                hist[-1] > 0 and\
1472                hist[4] == hist[5] == hist[6] == hist[7] == hist[8] == 0:
1473                    # Danger of isolated degenerate triangles
1474                    # print self.timestepping_statistics(track_speeds=True) 
1475                   
1476                    # Find triangles in last bin
1477                    # FIXME - speed up using Numeric
1478                    d = 0
1479                    for i in range(self.number_of_full_triangles):
1480                        if self.max_speed[i] > bins[-1]:
1481                            msg = 'Time=%f: Ignoring isolated high ' %self.time
1482                            msg += 'speed triangle ' 
1483                            msg += '#%d of %d with max speed=%f'\
1484                                  %(i, self.number_of_full_triangles,
1485                                    self.max_speed[i])
1486                   
1487                            # print 'Found offending triangle', i,
1488                            # self.max_speed[i]
1489                            self.get_quantity('xmomentum').set_values(0.0, indices=[i])
1490                            self.get_quantity('ymomentum').set_values(0.0, indices=[i])
1491                            self.max_speed[i]=0.0
1492                            d += 1
1493                   
1494                    #print 'Adjusted %d triangles' %d       
1495                    #print self.timestepping_statistics(track_speeds=True)     
1496               
1497
1498                       
1499        # self.timestep is calculated from speed of characteristics
1500        # Apply CFL condition here
1501        timestep = min(self.CFL*self.flux_timestep, max_timestep)
1502
1503        # Record maximal and minimal values of timestep for reporting
1504        self.max_timestep = max(timestep, self.max_timestep)
1505        self.min_timestep = min(timestep, self.min_timestep)
1506
1507           
1508 
1509        # Protect against degenerate time steps
1510        if timestep < min_timestep:
1511
1512            # Number of consecutive small steps taken b4 taking action
1513            self.smallsteps += 1
1514
1515            if self.smallsteps > self.max_smallsteps:
1516                self.smallsteps = 0 # Reset
1517
1518                if self._order_ == 1:
1519                    msg = 'WARNING: Too small timestep %.16f reached '\
1520                          %timestep
1521                    msg += 'even after %d steps of 1 order scheme'\
1522                           %self.max_smallsteps
1523                    print msg
1524                    timestep = min_timestep  # Try enforcing min_step
1525
1526                    print self.timestepping_statistics(track_speeds=True)
1527
1528                    raise Exception, msg
1529                else:
1530                    # Try to overcome situation by switching to 1 order
1531                    self._order_ = 1
1532
1533        else:
1534            self.smallsteps = 0
1535            if self._order_ == 1 and self.default_order == 2:
1536                self._order_ = 2
1537
1538
1539        # Ensure that final time is not exceeded
1540        if finaltime is not None and self.time + timestep > finaltime :
1541            timestep = finaltime-self.time
1542
1543        # Ensure that model time is aligned with yieldsteps
1544        if self.yieldtime + timestep > yieldstep:
1545            timestep = yieldstep-self.yieldtime
1546
1547        self.timestep = timestep
1548
1549
1550
1551    def compute_forcing_terms(self):
1552        """If there are any forcing functions driving the system
1553        they should be defined in Domain subclass and appended to
1554        the list self.forcing_terms
1555        """
1556
1557        for f in self.forcing_terms:
1558            f(self)
1559
1560
1561
1562    def update_conserved_quantities(self):
1563        """Update vectors of conserved quantities using previously
1564        computed fluxes specified forcing functions.
1565        """
1566
1567        N = len(self) # Number_of_triangles
1568        d = len(self.conserved_quantities)
1569
1570        timestep = self.timestep
1571
1572        # Compute forcing terms
1573        self.compute_forcing_terms()
1574
1575        # Update conserved_quantities
1576        for name in self.conserved_quantities:
1577            Q = self.quantities[name]
1578            Q.update(timestep)
1579
1580            # Note that Q.explicit_update is reset by compute_fluxes
1581            # Where is Q.semi_implicit_update reset?
1582           
1583
1584    def update_ghosts(self):
1585        pass
1586
1587    def distribute_to_vertices_and_edges(self):
1588        """Extrapolate conserved quantities from centroid to
1589        vertices and edge-midpoints for each volume
1590
1591        Default implementation is straight first order,
1592        i.e. constant values throughout each element and
1593        no reference to non-conserved quantities.
1594        """
1595
1596        for name in self.conserved_quantities:
1597            Q = self.quantities[name]
1598            if self._order_ == 1:
1599                Q.extrapolate_first_order()
1600            elif self._order_ == 2:
1601                Q.extrapolate_second_order()
1602                #Q.limit()
1603            else:
1604                raise 'Unknown order'
1605            #Q.interpolate_from_vertices_to_edges()
1606
1607
1608    def centroid_norm(self, quantity, normfunc):
1609        """Calculate the norm of the centroid values
1610        of a specific quantity, using normfunc.
1611
1612        normfunc should take a list to a float.
1613
1614        common normfuncs are provided in the module utilities.norms
1615        """
1616        return normfunc(self.quantities[quantity].centroid_values)
1617
1618
1619
1620#------------------
1621# Initialise module
1622#------------------
1623
1624# Optimisation with psyco
1625from anuga.config import use_psyco
1626if use_psyco:
1627    try:
1628        import psyco
1629    except:
1630        import os
1631        if os.name == 'posix' and os.uname()[4] == 'x86_64':
1632            pass
1633            # Psyco isn't supported on 64 bit systems, but it doesn't matter
1634        else:
1635            msg = 'WARNING: psyco (speedup) could not import'+\
1636                  ', you may want to consider installing it'
1637            print msg
1638    else:
1639        psyco.bind(Domain.update_boundary)
1640        #psyco.bind(Domain.update_timestep) # Not worth it
1641        psyco.bind(Domain.update_conserved_quantities)
1642        psyco.bind(Domain.distribute_to_vertices_and_edges)
1643
1644
1645if __name__ == "__main__":
1646    pass
Note: See TracBrowser for help on using the repository browser.