source: inundation/pyvolution/generic_boundary_conditions.py @ 1900

Last change on this file since 1900 was 1900, checked in by ole, 18 years ago

Changed interpolation points to absolute UTM coordinates according to new
file_function interface

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