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

Last change on this file since 3678 was 3678, checked in by steve, 18 years ago

Added more limiting to cells near dry cells, use beta_*_dry

File size: 31.6 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, Christopher Zoppou
7   Geoscience Australia
8"""
9
10from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
11from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
12     import Boundary
13from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
14     import File_boundary
15from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
16     import Dirichlet_boundary
17from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
18     import Time_boundary
19from anuga.abstract_2d_finite_volumes.generic_boundary_conditions\
20     import Transmissive_boundary
21
22from anuga.abstract_2d_finite_volumes.pmesh2domain import pmesh_to_domain
23from anuga.abstract_2d_finite_volumes.region\
24     import Set_region as region_set_region
25
26import types
27
28class Domain(Mesh):
29
30
31    def __init__(self,
32                 source=None,
33                 triangles=None,
34                 boundary=None,
35                 conserved_quantities=None,
36                 other_quantities=None,
37                 tagged_elements=None,
38                 geo_reference=None,
39                 use_inscribed_circle=False,
40                 mesh_filename=None,
41                 use_cache=False,
42                 verbose=False,
43                 full_send_dict=None,
44                 ghost_recv_dict=None,
45                 processor=0,
46                 numproc=1):
47
48
49        """Instantiate generic computational Domain.
50
51        Input:
52          source:    Either a mesh filename or coordinates of mesh vertices.
53                     If it is a filename values specified for triangles will
54                     be overridden.
55          triangles: Mesh connectivity (see mesh.py for more information)
56          boundary:  See mesh.py for more information
57
58          conserved_quantities: List of quantity names entering the
59                                conservation equations
60          other_quantities:     List of other quantity names
61
62          tagged_elements:
63          ...
64
65
66        """
67
68        # Determine whether source is a mesh filename or coordinates
69        if type(source) == types.StringType:
70            mesh_filename = source
71        else:
72            coordinates = source
73
74
75        # In case a filename has been specified, extract content
76        if mesh_filename is not None:
77            coordinates, triangles, boundary, vertex_quantity_dict, \
78                         tagged_elements, geo_reference = \
79                         pmesh_to_domain(file_name=mesh_filename,
80                                         use_cache=use_cache,
81                                         verbose=verbose)
82
83
84        # Initialise underlying mesh structure
85        Mesh.__init__(self, coordinates, triangles, boundary,
86                      tagged_elements, geo_reference, use_inscribed_circle,
87                      verbose=verbose)
88
89        if verbose: print 'Initialising Domain'
90        from Numeric import zeros, Float, Int, ones
91        from quantity import Quantity, Conserved_quantity
92
93        # List of quantity names entering
94        # the conservation equations
95        if conserved_quantities is None:
96            self.conserved_quantities = []
97        else:
98            self.conserved_quantities = conserved_quantities
99
100        # List of other quantity names
101        if other_quantities is None:
102            self.other_quantities = []
103        else:
104            self.other_quantities = other_quantities
105
106
107        #Build dictionary of Quantity instances keyed by quantity names
108        self.quantities = {}
109
110        #FIXME: remove later - maybe OK, though....
111        for name in self.conserved_quantities:
112            self.quantities[name] = Conserved_quantity(self)
113        for name in self.other_quantities:
114            self.quantities[name] = Quantity(self)
115
116        #Create an empty list for explicit forcing terms
117        self.forcing_terms = []
118
119        #Setup the ghost cell communication
120        if full_send_dict is None:
121            self.full_send_dict = {}
122        else:
123            self.full_send_dict  = full_send_dict
124
125        # List of other quantity names
126        if ghost_recv_dict  is None:
127            self.ghost_recv_dict  = {}
128        else:
129            self.ghost_recv_dict  = ghost_recv_dict
130
131        self.processor = processor
132        self.numproc   = numproc
133
134        # Setup Communication Buffers
135
136        if verbose: print 'Domain: Set up communication buffers (parallel)'
137        self.nsys = len(self.conserved_quantities)
138        for key in self.full_send_dict:
139            buffer_shape = self.full_send_dict[key][0].shape[0]
140            self.full_send_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
141
142
143        for key in self.ghost_recv_dict:
144            buffer_shape = self.ghost_recv_dict[key][0].shape[0]
145            self.ghost_recv_dict[key].append(zeros( (buffer_shape,self.nsys) ,Float))
146
147
148        # Setup cell full flag
149        # =1 for full
150        # =0 for ghost
151        N=self.number_of_elements
152        self.tri_full_flag = ones(N, Int)
153        for i in self.ghost_recv_dict.keys():
154            for id in self.ghost_recv_dict[i][0]:
155                self.tri_full_flag[id] = 0
156
157
158        #Defaults
159        from anuga.config import max_smallsteps, beta_w, beta_h, epsilon, CFL
160        self.beta_w = beta_w
161        self.beta_h = beta_h
162        self.epsilon = epsilon
163
164        #FIXME: Maybe have separate orders for h-limiter and w-limiter?
165        #Or maybe get rid of order altogether and use beta_w and beta_h
166        self.set_default_order(1)
167        #self.default_order = 1
168        #self._order_ = self.default_order
169
170        self.smallsteps = 0
171        self.max_smallsteps = max_smallsteps
172        self.number_of_steps = 0
173        self.number_of_first_order_steps = 0
174        self.CFL = CFL
175
176        self.boundary_map = None  # Will be populated by set_boundary       
177       
178
179        #Model time
180        self.time = 0.0
181        self.finaltime = None
182        self.min_timestep = self.max_timestep = 0.0
183        self.starttime = 0 #Physical starttime if any (0 is 1 Jan 1970 00:00:00)
184
185        ######OBSOLETE
186        #Origin in UTM coordinates
187        #FIXME: This should be set if read by a msh file
188        #self.zone = zone
189        #self.xllcorner = xllcorner
190        #self.yllcorner = yllcorner
191
192
193        #Checkpointing and storage
194        from anuga.config import default_datadir
195        self.datadir = default_datadir
196        self.filename = 'domain'
197        self.checkpoint = False
198
199        #MH310505 To avoid calculating the flux across each edge twice, keep an integer (boolean) array,
200        #to be used during the flux calculation
201        N=self.number_of_elements
202        self.already_computed_flux = zeros((N, 3), Int)
203
204        if mesh_filename is not None:
205            # If the mesh file passed any quantity values
206            # , initialise with these values.
207            if verbose: print 'Domain: Initialising quantity values'
208            self.set_quantity_vertices_dict(vertex_quantity_dict)
209
210
211        if verbose: print 'Domain: Done'
212
213
214
215
216    def set_default_order(self, n):
217        """Set default (spatial) order to either 1 or 2
218        """
219
220        msg = 'Default order must be either 1 or 2. I got %s' %n
221        assert n in [1,2], msg
222
223        self.default_order = n
224        self._order_ = self.default_order
225
226
227    #Public interface to Domain
228    def get_conserved_quantities(self, vol_id, vertex=None, edge=None):
229        """Get conserved quantities at volume vol_id
230
231        If vertex is specified use it as index for vertex values
232        If edge is specified use it as index for edge values
233        If neither are specified use centroid values
234        If both are specified an exeception is raised
235
236        Return value: Vector of length == number_of_conserved quantities
237
238        """
239
240        from Numeric import zeros, Float
241
242        if not (vertex is None or edge is None):
243            msg = 'Values for both vertex and edge was specified.'
244            msg += 'Only one (or none) is allowed.'
245            raise msg
246
247        q = zeros( len(self.conserved_quantities), Float)
248
249        for i, name in enumerate(self.conserved_quantities):
250            Q = self.quantities[name]
251            if vertex is not None:
252                q[i] = Q.vertex_values[vol_id, vertex]
253            elif edge is not None:
254                q[i] = Q.edge_values[vol_id, edge]
255            else:
256                q[i] = Q.centroid_values[vol_id]
257
258        return q
259
260    def set_time(self, time=0.0):
261        """Set the model time (seconds)"""
262
263        self.time = time
264
265    def set_quantity_vertices_dict(self, quantity_dict):
266        """Set values for named quantities.
267        The index is the quantity
268
269        name: Name of quantity
270        X: Compatible list, Numeric array, const or function (see below)
271
272        The values will be stored in elements following their
273        internal ordering.
274
275        """
276        for key in quantity_dict.keys():
277            self.set_quantity(key, quantity_dict[key], location='vertices')
278
279
280    def set_quantity(self, name, *args, **kwargs):
281        """Set values for named quantity
282
283
284        One keyword argument is documented here:
285        expression = None, # Arbitrary expression
286
287        expression:
288          Arbitrary expression involving quantity names
289
290        See Quantity.set_values for further documentation.
291        """
292
293        #FIXME (Ole): Allow new quantities here
294        #from quantity import Quantity, Conserved_quantity
295        #Create appropriate quantity object
296        ##if name in self.conserved_quantities:
297        ##    self.quantities[name] = Conserved_quantity(self)
298        ##else:
299        ##    self.quantities[name] = Quantity(self)
300
301
302        #Do the expression stuff
303        if kwargs.has_key('expression'):
304            expression = kwargs['expression']
305            del kwargs['expression']
306
307            Q = self.create_quantity_from_expression(expression)
308            kwargs['quantity'] = Q
309
310        #Assign values
311        self.quantities[name].set_values(*args, **kwargs)
312
313
314    def get_quantity(self, name, location='vertices', indices = None):
315        """Get quantity object.
316
317        name: Name of quantity
318
319        See methods inside the quantity object for more options
320        """
321
322        return self.quantities[name] #.get_values( location, indices = indices)
323
324
325    def get_quantity_object(self, name):
326        """Get object for named quantity
327
328        name: Name of quantity
329
330        FIXME: Obsolete
331        """
332
333        print 'get_quantity_object has been deprecated. Please use get_quantity'
334        return self.quantities[name]
335
336
337    def create_quantity_from_expression(self, expression):
338        """Create new quantity from other quantities using arbitrary expression
339
340        Combine existing quantities in domain using expression and return
341        result as a new quantity.
342
343        Note, the new quantity could e.g. be used in set_quantity
344
345        Valid expressions are limited to operators defined in class Quantity
346
347        Example:
348
349
350        """
351
352        from anuga.abstract_2d_finite_volumes.util import apply_expression_to_dictionary
353        return apply_expression_to_dictionary(expression, self.quantities)
354
355
356
357    def modify_boundary(self, boundary_map):
358        """Modify existing boundary by elements in boundary map
359
360        Input:
361
362        boundary_map: Dictionary mapping tags to boundary objects
363
364        See set_boundary for more details on how this works
365        """
366
367        for key in boundary_map.keys():
368            self.boundary_map[key] = boundary_map[key]
369
370        self.set_boundary(self.boundary_map)
371       
372       
373
374    def set_boundary(self, boundary_map):
375        """Associate boundary objects with tagged boundary segments.
376
377        Input boundary_map is a dictionary of boundary objects keyed
378        by symbolic tags to matched against tags in the internal dictionary
379        self.boundary.
380
381        As result one pointer to a boundary object is stored for each vertex
382        in the list self.boundary_objects.
383        More entries may point to the same boundary object
384
385        Schematically the mapping is from two dictionaries to one list
386        where the index is used as pointer to the boundary_values arrays
387        within each quantity.
388
389        self.boundary:          (vol_id, edge_id): tag
390        boundary_map (input):   tag: boundary_object
391        ----------------------------------------------
392        self.boundary_objects:  ((vol_id, edge_id), boundary_object)
393
394
395        Pre-condition:
396          self.boundary has been built.
397
398        Post-condition:
399          self.boundary_objects is built
400
401        If a tag from the domain doesn't appear in the input dictionary an
402        exception is raised.
403        However, if a tag is not used to the domain, no error is thrown.
404        FIXME: This would lead to implementation of a
405        default boundary condition
406
407        Note: If a segment is listed in the boundary dictionary and if it is
408        not None, it *will* become a boundary -
409        even if there is a neighbouring triangle.
410        This would be the case for internal boundaries
411
412        Boundary objects that are None will be skipped.
413
414        FIXME: If set_boundary is called multiple times and if Boundary
415        object is changed into None, the neighbour structure will not be
416        restored!!!
417
418
419        """
420
421        self.boundary_objects = []
422        self.boundary_map = boundary_map  #Store for use with eg. boundary_stats.
423
424        #FIXME: Try to remove the sorting and fix test_mesh.py
425        x = self.boundary.keys()
426        x.sort()
427
428        #Loop through edges that lie on the boundary and associate them with
429        #callable boundary objects depending on their tags
430        for k, (vol_id, edge_id) in enumerate(x):
431            tag = self.boundary[ (vol_id, edge_id) ]
432
433            if boundary_map.has_key(tag):
434                B = boundary_map[tag]  #Get callable boundary object
435
436                if B is not None:
437                    self.boundary_objects.append( ((vol_id, edge_id), B) )
438                    self.neighbours[vol_id, edge_id] = -len(self.boundary_objects)
439                else:
440                    pass
441                    #FIXME: Check and perhaps fix neighbour structure
442
443
444            else:
445                msg = 'ERROR (domain.py): Tag "%s" has not been ' %tag
446                msg += 'bound to a boundary object.\n'
447                msg += 'All boundary tags defined in domain must appear '
448                msg += 'in the supplied dictionary.\n'
449                msg += 'The tags are: %s' %self.get_boundary_tags()
450                raise msg
451
452
453    def set_region(self, *args, **kwargs):
454        """
455        This method is used to set quantities based on a regional tag.
456       
457        It is most often called with the following parameters;
458        (self, tag, quantity, X, location='vertices')
459        tag: the name of the regional tag used to specify the region
460        quantity: Name of quantity to change
461        X: const or function - how the quantity is changed
462        location: Where values are to be stored.
463            Permissible options are: vertices, centroid and unique vertices
464
465        A callable region class or a list of callable region classes
466        can also be passed into this function.
467        """
468        #print "*args", args
469        #print "**kwargs", kwargs
470        if len(args) == 1:
471            self._set_region(*args, **kwargs)
472        else:
473            #Assume it is arguments for the region.set_region function
474            func = region_set_region(*args, **kwargs)
475            self._set_region(func)
476           
477       
478    def _set_region(self, functions):
479        # The order of functions in the list is used.
480        if type(functions) not in [types.ListType,types.TupleType]:
481            functions = [functions]
482        for function in functions:
483            for tag in self.tagged_elements.keys():
484                function(tag, self.tagged_elements[tag], self)
485
486
487    #MISC
488    def check_integrity(self):
489        Mesh.check_integrity(self)
490
491        for quantity in self.conserved_quantities:
492            msg = 'Conserved quantities must be a subset of all quantities'
493            assert quantity in self.quantities, msg
494
495        ##assert hasattr(self, 'boundary_objects')
496
497    def write_time(self):
498        print self.timestepping_statistics()
499
500        #Old version
501        #if self.min_timestep == self.max_timestep:
502        #    print 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
503        #          %(self.time, self.min_timestep, self.number_of_steps,
504        #            self.number_of_first_order_steps)
505        #elif self.min_timestep > self.max_timestep:
506        #    print 'Time = %.4f, steps=%d (%d)'\
507        #          %(self.time, self.number_of_steps,
508        #            self.number_of_first_order_steps)
509        #else:
510        #    print 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
511        #          %(self.time, self.min_timestep,
512        #            self.max_timestep, self.number_of_steps,
513        #            self.number_of_first_order_steps)
514
515    def timestepping_statistics(self):
516        """Return string with time stepping statistics for printing or logging
517        """
518
519        msg = ''
520        if self.min_timestep == self.max_timestep:
521            msg += 'Time = %.4f, delta t = %.8f, steps=%d (%d)'\
522                   %(self.time, self.min_timestep, self.number_of_steps,
523                     self.number_of_first_order_steps)
524        elif self.min_timestep > self.max_timestep:
525            msg += 'Time = %.4f, steps=%d (%d)'\
526                   %(self.time, self.number_of_steps,
527                     self.number_of_first_order_steps)
528        else:
529            msg += 'Time = %.4f, delta t in [%.8f, %.8f], steps=%d (%d)'\
530                   %(self.time, self.min_timestep,
531                     self.max_timestep, self.number_of_steps,
532                     self.number_of_first_order_steps)
533
534        return msg
535
536
537    def write_boundary_statistics(self, quantities = None, tags = None):
538        print self.boundary_statistics(quantities, tags)
539
540    def boundary_statistics(self, quantities = None, tags = None):
541        """Output statistics about boundary forcing at each timestep
542
543
544        Input:
545          quantities: either None, a string or a list of strings naming the quantities to be reported
546          tags:       either None, a string or a list of strings naming the tags to be reported
547
548
549        Example output:
550        Tag 'wall':
551            stage in [2, 5.5]
552            xmomentum in []
553            ymomentum in []
554        Tag 'ocean'
555
556
557        If quantities are specified only report on those. Otherwise take all conserved quantities.
558        If tags are specified only report on those, otherwise take all tags.
559
560        """
561
562        #Input checks
563        import types, string
564
565        if quantities is None:
566            quantities = self.conserved_quantities
567        elif type(quantities) == types.StringType:
568            quantities = [quantities] #Turn it into a list
569
570        msg = 'Keyword argument quantities must be either None, '
571        msg += 'string or list. I got %s' %str(quantities)
572        assert type(quantities) == types.ListType, msg
573
574
575        if tags is None:
576            tags = self.get_boundary_tags()
577        elif type(tags) == types.StringType:
578            tags = [tags] #Turn it into a list
579
580        msg = 'Keyword argument tags must be either None, '
581        msg += 'string or list. I got %s' %str(tags)
582        assert type(tags) == types.ListType, msg
583
584        #Determine width of longest quantity name (for cosmetic purposes)
585        maxwidth = 0
586        for name in quantities:
587            w = len(name)
588            if w > maxwidth:
589                maxwidth = w
590
591        #Output stats
592        msg = 'Boundary values at time %.4f:\n' %self.time
593        for tag in tags:
594            msg += '    %s:\n' %tag
595
596            for name in quantities:
597                q = self.quantities[name]
598
599                #Find range of boundary values for tag and q
600                maxval = minval = None
601                for i, ((vol_id, edge_id), B) in\
602                        enumerate(self.boundary_objects):
603                    if self.boundary[(vol_id, edge_id)] == tag:
604                        v = q.boundary_values[i]
605                        if minval is None or v < minval: minval = v
606                        if maxval is None or v > maxval: maxval = v
607
608                if minval is None or maxval is None:
609                    msg += '        Sorry no information available about' +\
610                           ' tag %s and quantity %s\n' %(tag, name)
611                else:
612                    msg += '        %s in [%12.8f, %12.8f]\n'\
613                           %(string.ljust(name, maxwidth), minval, maxval)
614
615
616        return msg
617
618
619    def get_name(self):
620        return self.filename
621
622    def set_name(self, name):
623        self.filename = name
624
625    def get_datadir(self):
626        return self.datadir
627
628    def set_datadir(self, name):
629        self.datadir = name
630
631
632
633    #def set_defaults(self):
634    #    """Set default values for uninitialised quantities.
635    #    Should be overridden or specialised by specific modules
636    #    """#
637    #
638    #    for name in self.conserved_quantities + self.other_quantities:
639    #        self.set_quantity(name, 0.0)
640
641
642    ###########################
643    #Main components of evolve
644
645    def evolve(self,
646               yieldstep = None,
647               finaltime = None,
648               duration = None,
649               skip_initial_step = False):
650        """Evolve model through time starting from self.starttime.
651
652
653        yieldstep: Interval between yields where results are stored,
654                   statistics written and domain inspected or
655                   possibly modified. If omitted the internal predefined
656                   max timestep is used.
657                   Internally, smaller timesteps may be taken.
658
659        duration: Duration of simulation
660
661        finaltime: Time where simulation should end
662
663        If both duration and finaltime are given an exception is thrown.
664
665
666        skip_initial_step: Boolean flag that decides whether the first
667        yield step is skipped or not. This is useful for example to avoid
668        duplicate steps when multiple evolve processes are dove tailed.
669
670
671        Evolve is implemented as a generator and is to be called as such, e.g.
672
673        for t in domain.evolve(yieldstep, finaltime):
674            <Do something with domain and t>
675
676
677        All times are given in seconds
678
679        """
680
681        from anuga.config import min_timestep, max_timestep, epsilon
682
683        #FIXME: Maybe lump into a larger check prior to evolving
684        msg = 'Boundary tags must be bound to boundary objects before evolving system, '
685        msg += 'e.g. using the method set_boundary.\n'
686        msg += 'This system has the boundary tags %s '\
687               %self.get_boundary_tags()
688        assert hasattr(self, 'boundary_objects'), msg
689
690        ##self.set_defaults()
691
692        if yieldstep is None:
693            yieldstep = max_timestep
694        else:
695            yieldstep = float(yieldstep)
696
697        self._order_ = self.default_order
698
699
700        if finaltime is not None and duration is not None:
701            print 'F', finaltime, duration
702            msg = 'Only one of finaltime and duration may be specified'
703            raise msg
704        else:
705            if finaltime is not None:
706                self.finaltime = float(finaltime)
707            if duration is not None:
708                self.finaltime = self.starttime + float(duration)
709
710
711
712
713        self.yieldtime = 0.0 #Time between 'yields'
714
715        #Initialise interval of timestep sizes (for reporting only)
716        self.min_timestep = max_timestep
717        self.max_timestep = min_timestep
718        self.number_of_steps = 0
719        self.number_of_first_order_steps = 0
720
721        #update ghosts
722        self.update_ghosts()
723
724        #Initial update of vertex and edge values
725        self.distribute_to_vertices_and_edges()
726
727        #Initial update boundary values
728        self.update_boundary()
729
730        #Or maybe restore from latest checkpoint
731        if self.checkpoint is True:
732            self.goto_latest_checkpoint()
733
734        if skip_initial_step is False:
735            yield(self.time)  #Yield initial values
736
737        while True:
738
739            #Compute fluxes across each element edge
740            self.compute_fluxes()
741
742            #Update timestep to fit yieldstep and finaltime
743            self.update_timestep(yieldstep, finaltime)
744
745            #Update conserved quantities
746            self.update_conserved_quantities()
747
748            #update ghosts
749            self.update_ghosts()
750
751            #Update vertex and edge values
752            self.distribute_to_vertices_and_edges()
753
754            #Update boundary values
755            self.update_boundary()
756
757            #Update time
758            self.time += self.timestep
759            self.yieldtime += self.timestep
760            self.number_of_steps += 1
761            if self._order_ == 1:
762                self.number_of_first_order_steps += 1
763
764            #Yield results
765            if finaltime is not None and self.time >= finaltime:
766
767                if self.time > finaltime:
768                    #FIXME (Ole, 30 April 2006): Do we need this check?
769                    print 'WARNING (domain.py): time overshot finaltime. Contact Ole.Nielsen@ga.gov.au'
770                    self.time = finaltime
771
772                # Yield final time and stop
773                yield(self.time)
774                break
775
776
777            if self.yieldtime >= yieldstep:
778                # Yield (intermediate) time and allow inspection of domain
779
780                if self.checkpoint is True:
781                    self.store_checkpoint()
782                    self.delete_old_checkpoints()
783
784                #Pass control on to outer loop for more specific actions
785                yield(self.time)
786
787                # Reinitialise
788                self.yieldtime = 0.0
789                self.min_timestep = max_timestep
790                self.max_timestep = min_timestep
791                self.number_of_steps = 0
792                self.number_of_first_order_steps = 0
793
794
795    def evolve_to_end(self, finaltime = 1.0):
796        """Iterate evolve all the way to the end
797        """
798
799        for _ in self.evolve(yieldstep=None, finaltime=finaltime):
800            pass
801
802
803
804    def update_boundary(self):
805        """Go through list of boundary objects and update boundary values
806        for all conserved quantities on boundary.
807        """
808
809        #FIXME: Update only those that change (if that can be worked out)
810        #FIXME: Boundary objects should not include ghost nodes.
811        for i, ((vol_id, edge_id), B) in enumerate(self.boundary_objects):
812            if B is None:
813                print 'WARNING: Ignored boundary segment %d (None)'
814            else:
815                q = B.evaluate(vol_id, edge_id)
816
817                for j, name in enumerate(self.conserved_quantities):
818                    Q = self.quantities[name]
819                    Q.boundary_values[i] = q[j]
820
821
822    def compute_fluxes(self):
823        msg = 'Method compute_fluxes must be overridden by Domain subclass'
824        raise msg
825
826
827    def update_timestep(self, yieldstep, finaltime):
828
829        from anuga.config import min_timestep, max_timestep
830
831        # self.timestep is calculated from speed of characteristics
832        # Apply CFL condition here
833        timestep = min(self.CFL*self.timestep,max_timestep)
834
835        #Record maximal and minimal values of timestep for reporting
836        self.max_timestep = max(timestep, self.max_timestep)
837        self.min_timestep = min(timestep, self.min_timestep)
838
839        #Protect against degenerate time steps
840        if timestep < min_timestep:
841
842            #Number of consecutive small steps taken b4 taking action
843            self.smallsteps += 1
844
845            if self.smallsteps > self.max_smallsteps:
846                self.smallsteps = 0 #Reset
847
848                if self._order_ == 1:
849                    msg = 'WARNING: Too small timestep %.16f reached '\
850                          %timestep
851                    msg += 'even after %d steps of 1 order scheme'\
852                           %self.max_smallsteps
853                    print msg
854                    timestep = min_timestep  #Try enforcing min_step
855
856                    #raise msg
857                else:
858                    #Try to overcome situation by switching to 1 order
859                    self._order_ = 1
860
861        else:
862            self.smallsteps = 0
863            if self._order_ == 1 and self.default_order == 2:
864                self._order_ = 2
865
866
867        #Ensure that final time is not exceeded
868        if finaltime is not None and self.time + timestep > finaltime:
869            timestep = finaltime-self.time
870
871        #Ensure that model time is aligned with yieldsteps
872        if self.yieldtime + timestep > yieldstep:
873            timestep = yieldstep-self.yieldtime
874
875        self.timestep = timestep
876
877
878
879    def compute_forcing_terms(self):
880        """If there are any forcing functions driving the system
881        they should be defined in Domain subclass and appended to
882        the list self.forcing_terms
883        """
884
885        for f in self.forcing_terms:
886            f(self)
887
888
889
890    def update_conserved_quantities(self):
891        """Update vectors of conserved quantities using previously
892        computed fluxes specified forcing functions.
893        """
894
895        from Numeric import ones, sum, equal, Float
896
897        N = self.number_of_elements
898        d = len(self.conserved_quantities)
899
900        timestep = self.timestep
901
902        #Compute forcing terms
903        self.compute_forcing_terms()
904
905        #Update conserved_quantities
906        for name in self.conserved_quantities:
907            Q = self.quantities[name]
908            Q.update(timestep)
909
910            #Clean up
911            #Note that Q.explicit_update is reset by compute_fluxes
912
913            #MH090605 commented out the following since semi_implicit_update is now re-initialized
914            #at the end of the _update function in quantity_ext.c (This is called by the
915            #preceeding Q.update(timestep) statement above).
916            #For run_profile.py with N=128, the time of update_conserved_quantities is cut from 14.00 secs
917            #to 8.35 secs
918
919            #Q.semi_implicit_update[:] = 0.0
920
921    def update_ghosts(self):
922        pass
923
924    def distribute_to_vertices_and_edges(self):
925        """Extrapolate conserved quantities from centroid to
926        vertices and edge-midpoints for each volume
927
928        Default implementation is straight first order,
929        i.e. constant values throughout each element and
930        no reference to non-conserved quantities.
931        """
932
933        for name in self.conserved_quantities:
934            Q = self.quantities[name]
935            if self._order_ == 1:
936                Q.extrapolate_first_order()
937            elif self._order_ == 2:
938                Q.extrapolate_second_order()
939                Q.limit()
940            else:
941                raise 'Unknown order'
942            Q.interpolate_from_vertices_to_edges()
943
944
945    def centroid_norm(self, quantity, normfunc):
946        """Calculate the norm of the centroid values
947        of a specific quantity, using normfunc.
948
949        normfunc should take a list to a float.
950
951        common normfuncs are provided in the module utilities.norms
952        """
953        return normfunc(self.quantities[quantity].centroid_values)
954
955
956
957##############################################
958#Initialise module
959
960#Optimisation with psyco
961from anuga.config import use_psyco
962if use_psyco:
963    try:
964        import psyco
965    except:
966        import os
967        if os.name == 'posix' and os.uname()[4] == 'x86_64':
968            pass
969            #Psyco isn't supported on 64 bit systems, but it doesn't matter
970        else:
971            msg = 'WARNING: psyco (speedup) could not import'+\
972                  ', you may want to consider installing it'
973            print msg
974    else:
975        psyco.bind(Domain.update_boundary)
976        #psyco.bind(Domain.update_timestep)     #Not worth it
977        psyco.bind(Domain.update_conserved_quantities)
978        psyco.bind(Domain.distribute_to_vertices_and_edges)
979
980
981if __name__ == "__main__":
982    pass
Note: See TracBrowser for help on using the repository browser.