source: anuga_core/source/anuga/abstract_2d_finite_volumes/generic_boundary_conditions.py @ 6448

Last change on this file since 6448 was 6448, checked in by jgriffin, 15 years ago

updated Time_boundary to allow for default boundary condition beyond length of time series

File size: 13.7 KB
Line 
1"""boundary.py - Classes for implementing boundary conditions
2"""
3
4from warnings import warn
5
6from anuga.utilities.numerical_tools import NAN   
7from anuga.fit_interpolate.interpolate import Modeltime_too_late
8from anuga.fit_interpolate.interpolate import Modeltime_too_early
9
10import Numeric as num
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        self.conserved_quantities=num.array(conserved_quantities, num.Float)
72
73    def __repr__(self):
74        return 'Dirichlet boundary (%s)' %self.conserved_quantities
75
76    def evaluate(self, vol_id=None, edge_id=None):
77        return self.conserved_quantities
78
79
80
81class Time_boundary(Boundary):
82    """Time dependent boundary returns values for the
83    conserved quantities as a function of time.
84    Must specify domain to get access to model time and a function f(t)
85    which must return conserved quantities as a function time
86    """
87
88    # FIXME (Ole): We should rename f to function to be consistent with
89    # Transmissive_Momentum_Set_Stage_Boundary (cf posting by rrraman)
90    def __init__(self, domain = None, f = None, default_boundary = None):
91        Boundary.__init__(self)
92
93        try:
94            q = f(0.0)
95        except Exception, e:
96            msg = 'Function for time boundary could not be executed:\n%s' %e
97            raise msg
98
99
100        try:
101            q = num.array(q, num.Float)
102        except:
103            msg = 'Return value from time boundary function could '
104            msg += 'not be converted into a Numeric array of floats.\n'
105            msg += 'Specified function should return either list or array.\n'
106            msg += 'I got %s' %str(q)
107            raise msg
108
109        msg = 'ERROR: Time boundary function must return a 1d list or array '
110        assert len(q.shape) == 1, msg
111
112        d = len(domain.conserved_quantities)
113        msg = 'Return value for f must be a list or an array of length %d' %d
114        assert len(q) == d, msg
115
116        self.f = f
117        self.domain = domain
118
119    def __repr__(self):
120        return 'Time boundary'
121
122    def evaluate(self, vol_id=None, edge_id=None):
123        # FIXME (Ole): I think this should be get_time(), see ticket:306
124        try:
125            res = self.f(self.domain.time)
126        except Modeltime_too_early, e:
127            raise Modeltime_too_early, e
128        except Modeltime_too_late, e:
129            if self.default_boundary is None:
130                raise Exception, e # Reraise exception
131            else:
132                # Pass control to default boundary
133                res = self.default_boundary.evaluate(vol_id, edge_id)
134               
135                # Ensure that result cannot be manipulated
136                # This is a real danger in case the
137                # default_boundary is a Dirichlet type
138                # for instance.
139                res = res.copy() 
140               
141                if self.default_boundary_invoked is False:
142                    # Issue warning the first time
143                    msg = '%s' %str(e)
144                    msg += 'Instead I will use the default boundary: %s\n'\
145                        %str(self.default_boundary) 
146                    msg += 'Note: Further warnings will be supressed'
147                    warn(msg)
148               
149                    # FIXME (Ole): Replace this crude flag with
150                    # Python's ability to print warnings only once.
151                    # See http://docs.python.org/lib/warning-filter.html
152                    self.default_boundary_invoked = True
153
154        return res
155
156
157
158class File_boundary(Boundary):
159    """The File_boundary reads values for the conserved
160    quantities from an sww NetCDF file, and returns interpolated values
161    at the midpoints of each associated boundary segment.
162    Time dependency is interpolated linearly.
163
164    Assumes that file contains a time series and possibly
165    also spatial info. See docstring for File_function in util.py
166    for details about admissible file formats
167
168    File boundary must read and interpolate from *smoothed* version
169    as stored in sww and cannot work with the discontinuos triangles.
170
171    Example:
172    Bf = File_boundary('source_file.sww', domain)
173
174
175    Note that the resulting solution history is not exactly the same as if
176    the models were coupled as there is no feedback into the source model.
177   
178    Optional keyword argument default_boundary must be either None or
179    an instance of class descending from class Boundary.
180    This will be used in case model time exceeds that available in the
181    underlying data.
182       
183    """
184
185    # FIXME (Ole): I kind of like the name Spatio_Temporal_boundary for this
186    # rather than File_boundary
187
188    def __init__(self, filename, domain, 
189                 time_thinning=1, 
190                 time_limit=None,
191                 boundary_polygon=None,   
192                 default_boundary=None,
193                 use_cache=False, 
194                 verbose=False): 
195
196        import time
197        from anuga.config import time_format
198        from anuga.abstract_2d_finite_volumes.util import file_function
199
200        Boundary.__init__(self)
201
202        # Get x,y vertex coordinates for all triangles
203        V = domain.vertex_coordinates
204
205        # Compute midpoint coordinates for all boundary elements
206        # Only a subset may be invoked when boundary is evaluated but
207        # we don't know which ones at this stage since this object can
208        # be attached to
209        # any tagged boundary later on.
210
211        if verbose: print 'Find midpoint coordinates of entire boundary'
212        self.midpoint_coordinates = num.zeros((len(domain.boundary), 2), num.Float)
213        boundary_keys = domain.boundary.keys()
214
215        xllcorner = domain.geo_reference.get_xllcorner()
216        yllcorner = domain.geo_reference.get_yllcorner()       
217       
218
219        # Make ordering unique #FIXME: should this happen in domain.py?
220        boundary_keys.sort()
221
222        # Record ordering #FIXME: should this also happen in domain.py?
223        self.boundary_indices = {}
224        for i, (vol_id, edge_id) in enumerate(boundary_keys):
225
226            base_index = 3*vol_id
227            x0, y0 = V[base_index, :]
228            x1, y1 = V[base_index+1, :]
229            x2, y2 = V[base_index+2, :]
230           
231            # Compute midpoints
232            if edge_id == 0: m = num.array([(x1 + x2)/2, (y1 + y2)/2], num.Float)
233            if edge_id == 1: m = num.array([(x0 + x2)/2, (y0 + y2)/2], num.Float)
234            if edge_id == 2: m = num.array([(x1 + x0)/2, (y1 + y0)/2], num.Float)
235
236            # Convert to absolute UTM coordinates
237            m[0] += xllcorner
238            m[1] += yllcorner
239           
240            # Register point and index
241            self.midpoint_coordinates[i,:] = m
242
243            # Register index of this boundary edge for use with evaluate
244            self.boundary_indices[(vol_id, edge_id)] = i
245
246        if verbose: print 'Initialise file_function'
247        self.F = file_function(filename,
248                               domain,
249                               quantities=domain.conserved_quantities,
250                               interpolation_points=self.midpoint_coordinates,
251                               time_thinning=time_thinning,
252                               time_limit=time_limit,
253                               use_cache=use_cache, 
254                               verbose=verbose,
255                               boundary_polygon=boundary_polygon)
256                             
257        # Check and store default_boundary
258        msg = 'Keyword argument default_boundary must be either None '
259        msg += 'or a boundary object.\n I got %s' %(str(default_boundary))
260        assert default_boundary is None or\
261            isinstance(default_boundary, Boundary), msg
262        self.default_boundary = default_boundary
263        self.default_boundary_invoked = False    # Flag
264
265        # Store pointer to domain and verbosity
266        self.domain = domain
267        self.verbose = verbose
268
269
270        # Here we'll flag indices outside the mesh as a warning
271        # as suggested by Joaquim Luis in sourceforge posting
272        # November 2007
273        # We won't make it an error as it is conceivable that
274        # only part of mesh boundary is actually used with a given
275        # file boundary sww file.
276        if hasattr(self.F, 'indices_outside_mesh') and\
277               len(self.F.indices_outside_mesh) > 0:
278            msg = 'WARNING: File_boundary has points outside the mesh '
279            msg += 'given in %s. ' %filename
280            msg += 'See warning message issued by Interpolation_function '
281            msg += 'for details (should appear above somewhere if '
282            msg += 'verbose is True).\n'
283            msg += 'This is perfectly OK as long as the points that are '
284            msg += 'outside aren\'t used on the actual boundary segment.'
285            if verbose is True:           
286                print msg
287            #raise Exception(msg)
288
289        # Test that file function can be called
290        q = self.F(0, point_id=0)
291        d = len(domain.conserved_quantities)
292        msg = 'Values specified in file %s must be ' %filename
293        msg += ' a list or an array of length %d' %d
294        assert len(q) == d, msg
295
296
297    def __repr__(self):
298        return 'File boundary'
299
300
301    def evaluate(self, vol_id=None, edge_id=None):
302        """Return linearly interpolated values based on domain.time
303        at midpoint of segment defined by vol_id and edge_id.
304        """
305
306        # FIXME (Ole): I think this should be get_time(), see ticket:306
307        t = self.domain.time
308       
309        if vol_id is not None and edge_id is not None:
310            i = self.boundary_indices[ vol_id, edge_id ]
311           
312            try:
313                res = self.F(t, point_id=i)
314            except Modeltime_too_early, e:
315                raise Modeltime_too_early, e
316            except Modeltime_too_late, e:
317                if self.default_boundary is None:
318                    raise Exception, e # Reraise exception
319                else:
320                    # Pass control to default boundary
321                    res = self.default_boundary.evaluate(vol_id, edge_id)
322                   
323                    # Ensure that result cannot be manipulated
324                    # This is a real danger in case the
325                    # default_boundary is a Dirichlet type
326                    # for instance.
327                    res = res.copy() 
328                   
329                    if self.default_boundary_invoked is False:
330                        # Issue warning the first time
331                        msg = '%s' %str(e)
332                        msg += 'Instead I will use the default boundary: %s\n'\
333                            %str(self.default_boundary) 
334                        msg += 'Note: Further warnings will be supressed'
335                        warn(msg)
336                   
337                        # FIXME (Ole): Replace this crude flag with
338                        # Python's ability to print warnings only once.
339                        # See http://docs.python.org/lib/warning-filter.html
340                        self.default_boundary_invoked = True
341                   
342
343            if res == NAN:
344                x,y=self.midpoint_coordinates[i,:]
345                msg = 'NAN value found in file_boundary at '
346                msg += 'point id #%d: (%.2f, %.2f).\n' %(i, x, y)
347
348                if hasattr(self.F, 'indices_outside_mesh') and\
349                       len(self.F.indices_outside_mesh) > 0:
350                    # Check if NAN point is due it being outside
351                    # boundary defined in sww file.
352
353                    if i in self.F.indices_outside_mesh:
354                        msg += 'This point refers to one outside the '
355                        msg += 'mesh defined by the file %s.\n'\
356                               %self.F.filename
357                        msg += 'Make sure that the file covers '
358                        msg += 'the boundary segment it is assigned to '
359                        msg += 'in set_boundary.'
360                    else:
361                        msg += 'This point is inside the mesh defined '
362                        msg += 'the file %s.\n' %self.F.filename
363                        msg += 'Check this file for NANs.'
364                raise Exception, msg
365           
366            return res
367        else:
368            msg = 'Boundary call without point_id not implemented.\n'
369            msg += 'vol_id=%s, edge_id=%s' %(str(vol_id), str(edge_id))
370            raise Exception, msg
Note: See TracBrowser for help on using the repository browser.