source: anuga_core/source_numpy_conversion/anuga/abstract_2d_finite_volumes/generic_boundary_conditions.py @ 5899

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

Initial NumPy? changes (again!).

File size: 16.7 KB
Line 
1## Automatically adapted for numpy.oldnumeric Oct 28, 2008 by alter_code1.py
2
3"""boundary.py - Classes for implementing boundary conditions
4"""
5
6from warnings import warn
7
8from anuga.utilities.numerical_tools import NAN   
9from anuga.fit_interpolate.interpolate import Modeltime_too_late
10from anuga.fit_interpolate.interpolate import Modeltime_too_early
11
12
13class Boundary:
14    """Base class for boundary conditions.
15       Specific boundary conditions must provide values for
16       the conserved_quantities
17
18       A boundary object has one neighbour; the one it
19       serves.
20    """
21
22    def __init__(self):
23        pass
24
25    def evaluate(self, vol_id=None, edge_id=None):
26        msg = 'Generic class Boundary must be subclassed'
27        raise Exception, msg
28
29
30class Transmissive_boundary(Boundary):
31    """Transmissive boundary returns same conserved quantities as
32    those present in its neighbour volume.
33
34    Underlying domain must be specified when boundary is instantiated
35    """
36
37    def __init__(self, domain = None):
38        Boundary.__init__(self)
39
40        if domain is None:
41            msg = 'Domain must be specified for transmissive boundary'
42            raise Exception, msg
43
44        self.domain = domain
45
46    def __repr__(self):
47        return 'Transmissive_boundary(%s)' %self.domain
48
49    def evaluate(self, vol_id, edge_id):
50        """Transmissive boundaries return the edge values
51        of the volume they serve.
52        """
53
54        q = self.domain.get_conserved_quantities(vol_id, edge = edge_id)
55        return q
56
57
58class Dirichlet_boundary(Boundary):
59    """Dirichlet boundary returns constant values for the
60    conserved quantities
61    """
62
63
64    def __init__(self, conserved_quantities=None):
65        Boundary.__init__(self)
66
67        if conserved_quantities is None:
68            msg = 'Must specify one value for each conserved quantity'
69            raise Exception, msg
70
71#        from numpy.oldnumeric import array, Float
72        from numpy import array, float
73        self.conserved_quantities=array(conserved_quantities).astype(float)
74
75    def __repr__(self):
76        return 'Dirichlet boundary (%s)' %self.conserved_quantities
77
78    def evaluate(self, vol_id=None, edge_id=None):
79        return self.conserved_quantities
80
81
82
83class Time_boundary(Boundary):
84    """Time dependent boundary returns values for the
85    conserved quantities as a function of time.
86    Must specify domain to get access to model time and a function f(t)
87    which must return conserved quantities as a function time
88    """
89
90    # FIXME (Ole): We should rename f to function to be consistent with
91    # Transmissive_Momentum_Set_Stage_Boundary (cf posting by rrraman)
92    def __init__(self, domain = None, f = None):
93        Boundary.__init__(self)
94
95        try:
96            q = f(0.0)
97        except Exception, e:
98            msg = 'Function for time boundary could not be executed:\n%s' %e
99            raise msg
100
101
102#        from numpy.oldnumeric import array, Float
103        from numpy import array, float
104        try:
105            q = array(q).astype(float)
106        except:
107            msg = 'Return value from time boundary function could '
108            msg += 'not be converted into a Numeric array of floats.\n'
109            msg += 'Specified function should return either list or array.'
110            raise msg
111
112        msg = 'ERROR: Time boundary function must return a 1d list or array '
113        assert len(q.shape) == 1, msg
114
115        d = len(domain.conserved_quantities)
116        msg = 'Return value for f must be a list or an array of length %d' %d
117        assert len(q) == d, msg
118
119        self.f = f
120        self.domain = domain
121
122    def __repr__(self):
123        return 'Time boundary'
124
125    def evaluate(self, vol_id=None, edge_id=None):
126        return self.f(self.domain.time)
127
128
129class File_boundary_time(Boundary):
130    """Boundary values obtained from file and interpolated.
131    conserved quantities as a function of time.
132
133    Assumes that file contains a time series.
134
135    No spatial info assumed.
136    """
137
138    #FIXME: Is this still necessary
139
140    def __init__(self, filename, domain):
141        import time
142#        from numpy.oldnumeric import array
143        from numpy import array
144        from anuga.config import time_format
145        from anuga.abstract_2d_finite_volumes.util import File_function
146
147        Boundary.__init__(self)
148
149        self.F = File_function(filename, domain)
150        self.domain = domain
151
152        #Test
153        q = self.F(0)
154
155        d = len(domain.conserved_quantities)
156        msg = 'Values specified in file must be a list or an array of length %d' %d
157        assert len(q) == d, msg
158
159
160    def __repr__(self):
161        return 'File boundary'
162
163    def evaluate(self, vol_id=None, edge_id=None):
164        """Return linearly interpolated values based on domain.time
165
166        vol_id and edge_id are ignored
167        """
168
169        t = self.domain.time
170        return self.F(t)
171
172
173
174
175class File_boundary(Boundary):
176    """The File_boundary reads values for the conserved
177    quantities from an sww NetCDF file, and returns interpolated values
178    at the midpoints of each associated boundary segment.
179    Time dependency is interpolated linearly.
180
181    Assumes that file contains a time series and possibly
182    also spatial info. See docstring for File_function in util.py
183    for details about admissible file formats
184
185    File boundary must read and interpolate from *smoothed* version
186    as stored in sww and cannot work with the discontinuos triangles.
187
188    Example:
189    Bf = File_boundary('source_file.sww', domain)
190
191
192    Note that the resulting solution history is not exactly the same as if
193    the models were coupled as there is no feedback into the source model.
194   
195    Optional keyword argument default_boundary must be either None or
196    an instance of class descending from class Boundary.
197    This will be used in case model time exceeds that available in the
198    underlying data.
199       
200    """
201
202    # FIXME (Ole): I kind of like the name Spatio_Temporal_boundary for this
203    # rather than File_boundary
204
205    def __init__(self, filename, domain, 
206                 time_thinning=1, 
207                 boundary_polygon=None,   
208                 default_boundary=None,
209                 use_cache=False, 
210                 verbose=False): 
211
212        import time
213#        from numpy.oldnumeric import array, zeros, Float
214        from numpy import array, zeros, float
215        from anuga.config import time_format
216        from anuga.abstract_2d_finite_volumes.util import file_function
217
218        Boundary.__init__(self)
219
220        # Get x,y vertex coordinates for all triangles
221        V = domain.vertex_coordinates
222
223        # Compute midpoint coordinates for all boundary elements
224        # Only a subset may be invoked when boundary is evaluated but
225        # we don't know which ones at this stage since this object can
226        # be attached to
227        # any tagged boundary later on.
228
229        if verbose: print 'Find midpoint coordinates of entire boundary'
230        self.midpoint_coordinates = zeros( (len(domain.boundary), 2), float)
231        boundary_keys = domain.boundary.keys()
232
233        xllcorner = domain.geo_reference.get_xllcorner()
234        yllcorner = domain.geo_reference.get_yllcorner()       
235       
236
237        # Make ordering unique #FIXME: should this happen in domain.py?
238        boundary_keys.sort()
239
240        # Record ordering #FIXME: should this also happen in domain.py?
241        self.boundary_indices = {}
242        for i, (vol_id, edge_id) in enumerate(boundary_keys):
243
244            base_index = 3*vol_id
245            x0, y0 = V[base_index, :]
246            x1, y1 = V[base_index+1, :]
247            x2, y2 = V[base_index+2, :]
248           
249            # Compute midpoints
250            if edge_id == 0: m = array([(x1 + x2)/2, (y1 + y2)/2])
251            if edge_id == 1: m = array([(x0 + x2)/2, (y0 + y2)/2])
252            if edge_id == 2: m = array([(x1 + x0)/2, (y1 + y0)/2])
253
254            # Convert to absolute UTM coordinates
255            m[0] += xllcorner
256            m[1] += yllcorner
257           
258            # Register point and index
259            self.midpoint_coordinates[i,:] = m
260
261            # Register index of this boundary edge for use with evaluate
262            self.boundary_indices[(vol_id, edge_id)] = i
263
264        if verbose: print 'Initialise file_function'
265        self.F = file_function(filename,
266                               domain,
267                               quantities=domain.conserved_quantities,
268                               interpolation_points=self.midpoint_coordinates,
269                               time_thinning=time_thinning,
270                               use_cache=use_cache, 
271                               verbose=verbose,
272                               boundary_polygon=boundary_polygon)
273                             
274        # Check and store default_boundary
275        msg = 'Keyword argument default_boundary must be either None '
276        msg += 'or a boundary object.\n I got %s' %(str(default_boundary))
277        assert default_boundary is None or\
278            isinstance(default_boundary, Boundary), msg
279        self.default_boundary = default_boundary
280        self.default_boundary_invoked = False    # Flag
281
282        # Store pointer to domain
283        self.domain = domain
284       
285        self.verbose = verbose
286
287        # Test
288
289        # Here we'll flag indices outside the mesh as a warning
290        # as suggested by Joaquim Luis in sourceforge posting
291        # November 2007
292        # We won't make it an error as it is conceivable that
293        # only part of mesh boundary is actually used with a given
294        # file boundary sww file.
295        if hasattr(self.F, 'indices_outside_mesh') and\
296               len(self.F.indices_outside_mesh) > 0:
297            msg = 'WARNING: File_boundary has points outside the mesh '
298            msg += 'given in %s. ' %filename
299            msg += 'See warning message issued by Interpolation_function '
300            msg += 'for details (should appear above somewhere if '
301            msg += 'verbose is True).\n'
302            msg += 'This is perfectly OK as long as the points that are '
303            msg += 'outside aren\'t used on the actual boundary segment.'
304            if verbose is True:           
305                print msg
306            #raise Exception(msg)
307
308        # Test that file function can be called
309        q = self.F(0, point_id=0)
310        d = len(domain.conserved_quantities)
311        msg = 'Values specified in file %s must be ' %filename
312        msg += ' a list or an array of length %d' %d
313        assert len(q) == d, msg
314
315
316    def __repr__(self):
317        return 'File boundary'
318
319
320    def evaluate(self, vol_id=None, edge_id=None):
321        """Return linearly interpolated values based on domain.time
322        at midpoint of segment defined by vol_id and edge_id.
323        """
324
325        t = self.domain.time
326       
327        if vol_id is not None and edge_id is not None:
328            i = self.boundary_indices[ vol_id, edge_id ]
329           
330            try:
331                res = self.F(t, point_id=i)
332            except Modeltime_too_early, e:
333                raise Modeltime_too_early, e
334            except Modeltime_too_late, e:
335                if self.default_boundary is None:
336                    raise Exception, e # Reraise exception
337                else:
338                    # Pass control to default boundary
339                    res = self.default_boundary.evaluate(vol_id, edge_id)
340                   
341                    # Ensure that result cannot be manipulated
342                    # This is a real danger in case the
343                    # default_boundary is a Dirichlet type
344                    # for instance.
345                    res = res.copy() 
346                   
347                    if self.default_boundary_invoked is False:
348                        # Issue warning the first time
349                        msg = '%s' %str(e)
350                        msg += 'Instead I will use the default boundary: %s\n'\
351                            %str(self.default_boundary) 
352                        msg += 'Note: Further warnings will be supressed'
353                        warn(msg)
354                   
355                        # FIXME (Ole): Replace this crude flag with
356                        # Python's ability to print warnings only once.
357                        # See http://docs.python.org/lib/warning-filter.html
358                        self.default_boundary_invoked = True
359                   
360
361            if res == NAN:
362                x,y=self.midpoint_coordinates[i,:]
363                msg = 'NAN value found in file_boundary at '
364                msg += 'point id #%d: (%.2f, %.2f).\n' %(i, x, y)
365
366                if hasattr(self.F, 'indices_outside_mesh') and\
367                       len(self.F.indices_outside_mesh) > 0:
368                    # Check if NAN point is due it being outside
369                    # boundary defined in sww file.
370
371                    if i in self.F.indices_outside_mesh:
372                        msg += 'This point refers to one outside the '
373                        msg += 'mesh defined by the file %s.\n'\
374                               %self.F.filename
375                        msg += 'Make sure that the file covers '
376                        msg += 'the boundary segment it is assigned to '
377                        msg += 'in set_boundary.'
378                    else:
379                        msg += 'This point is inside the mesh defined '
380                        msg += 'the file %s.\n' %self.F.filename
381                        msg += 'Check this file for NANs.'
382                raise Exception, msg
383           
384            return res
385        else:
386            msg = 'Boundary call without point_id not implemented.\n'
387            msg += 'vol_id=%s, edge_id=%s' %(str(vol_id), str(edge_id))
388            raise Exception, msg
389            # FIXME: What should the semantics be?
390            #return self.F(t)
391
392
393
394
395
396
397
398
399#THIS FAR (10/8/4)
400class Connective_boundary(Boundary):
401    """Connective boundary returns values for the
402    conserved quantities from a volume as defined by a connection table
403    mapping between tuples of (volume id, face id) for volumes that
404    have their boundaries connected.
405
406    FIXME: Perhaps include possibility of mapping between
407    different domains as well
408
409    FIXME: In case of shallow water we may want to have a
410    special version that casts this in terms of height rather than stage
411    """
412
413
414    def __init__(self, table):
415        from domain import Volume
416
417        Boundary.__init__(self)
418
419        self.connection_table = table
420        self.Volume = Volume
421
422    def __repr__(self):
423        return 'Connective boundary'
424
425    #FIXME: IF we ever need to get field_values from connected volume,
426    #that method could be overridden here (using same idea as in
427    #get_conserved_quantities
428    #def get_field_values()
429
430    def get_conserved_quantities(self, volume, face=0):
431
432        id = volume.id
433        if self.connection_table.has_key((id, face)):
434            other_id, other_face = self.connection_table[(id, face)]
435
436            other_volume = self.Volume.instances[other_id]
437            cmd = 'q = other_volume.conserved_quantities_face%d' %face;
438            exec(cmd)
439            return q
440        else:
441            msg = 'Volume, face tuple ($d, %d) has not been mapped'\
442                  %(id, face)
443            raise msg
444
445
446
447
448
449#FIXME: Add a boundary with a general function of x,y, and t
450
451#FIXME: Add periodic boundaries e.g.:
452# Attempt at periodic conditions from advection_spik. Remember this
453#
454#first = 2*(N-1)*N
455#for i in range(1,2*N+1,2):
456#    k = first + i-1#
457#
458#    print i,k
459#
460#    domain[i].faces[2].neighbour = domain[k].faces[1]
461#    domain[k].faces[1].neighbour = domain[i].faces[2]
462
463
464
465class General_boundary(Boundary):
466    """General boundary which can compute conserved quantities based on
467    their previous value, conserved quantities of its neighbour and model time.
468
469    Must specify initial conserved quantities,
470    neighbour,
471    domain to get access to model time
472    a function f(q_old, neighbours_q, t) which must return
473    new conserved quantities q as a function time
474
475    FIXME: COMPLETE UNTESTED - JUST AN IDEA
476    """
477
478    def __init__(self, neighbour=None, conserved_quantities=None, domain=None, f=None):
479        Boundary.__init__(self, neighbour=neighbour, conserved_quantities=conserved_quantities)
480
481        self.f = f
482        self.domain = domain
483
484
485    def get_conserved_quantities(self, volume=None, face=0):
486        return self.f(self.conserved_quantities,
487                      neighbour.conserved_quantities,
488                      self.domain.time)
489
490
491
492
Note: See TracBrowser for help on using the repository browser.