source: anuga_core/source/anuga/abstract_2d_finite_volumes/domain.py @ 5892

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

Made numpy changes, lots of problems.

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