source: inundation/ga/storm_surge/pyvolution/generic_boundary_conditions.py @ 643

Last change on this file since 643 was 623, checked in by ole, 20 years ago

Created and tested general File_function (reading and interpolating time series from file) and used it to refactor and simplify File_boundary

File size: 10.9 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(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
128    def __init__(self, filename, domain):
129        import time
130        from Numeric import array
131        from config import time_format
132        from util import File_function
133       
134        Boundary.__init__(self)
135
136        self.F = File_function(filename, domain)
137        self.domain = domain
138
139        #Test
140        q = self.F(0)
141
142        d = len(domain.conserved_quantities)
143        msg = 'Values specified in file must be a list or an array of length %d' %d
144        assert len(q) == d, msg
145
146       
147    def __repr__(self):
148        return 'File boundary'
149
150    def evaluate(self, vol_id=None, edge_id=None):
151        """Return linearly interpolated values based on domain.time
152
153        vol_id and edge_id are ignored
154        """
155
156        t = self.domain.time
157        return self.F(t)
158
159
160
161
162#THIS FAR (10/8/4)
163class Connective_boundary(Boundary):
164    """Connective boundary returns values for the
165    conserved quantities from a volume as defined by a connection table
166    mapping between tuples of (volume id, face id) for volumes that
167    have their boundaries connected.
168
169    FIXME: Perhaps include possibility of mapping between
170    different domains as well
171
172    FIXME: In case of shallow water we may want to have a
173    special version that casts this in terms of height rather than stage
174    """
175
176
177    def __init__(self, table):
178        from domain import Volume
179       
180        Boundary.__init__(self)
181
182        self.connection_table = table
183        self.Volume = Volume
184
185    def __repr__(self):
186        return 'Connective boundary'       
187
188    #FIXME: IF we ever need to get field_values from connected volume,
189    #that method could be overridden here (using same idea as in
190    #get_conserved_quantities
191    #def get_field_values()
192   
193    def get_conserved_quantities(self, volume, face=0):
194
195        id = volume.id
196        if self.connection_table.has_key((id, face)):
197            other_id, other_face = self.connection_table[(id, face)] 
198           
199            other_volume = self.Volume.instances[other_id]
200            cmd = 'q = other_volume.conserved_quantities_face%d' %face;
201            exec(cmd)       
202            return q
203        else:
204            msg = 'Volume, face tuple ($d, %d) has not been mapped'\
205                  %(id, face)
206            raise msg
207           
208       
209
210
211
212#FIXME: Add a boundary with a general function of x,y, and t
213
214#FIXME: Add periodic boundaries e.g.:
215# Attempt at periodic conditions from advection_spik. Remember this
216#
217#first = 2*(N-1)*N
218#for i in range(1,2*N+1,2):
219#    k = first + i-1#
220#
221#    print i,k
222#   
223#    domain[i].faces[2].neighbour = domain[k].faces[1]
224#    domain[k].faces[1].neighbour = domain[i].faces[2]   
225   
226
227
228class General_boundary(Boundary):
229    """General boundary which can compute conserved quantities based on
230    their previous value, conserved quantities of its neighbour and model time.
231
232    Must specify initial conserved quantities,
233    neighbour,
234    domain to get access to model time
235    a function f(q_old, neighbours_q, t) which must return
236    new conserved quantities q as a function time
237
238    FIXME: COMPLETE UNTESTED - JUST AN IDEA
239    """
240
241    def __init__(self, neighbour=None, conserved_quantities=None, domain=None, f=None):
242        Boundary.__init__(self, neighbour=neighbour, conserved_quantities=conserved_quantities)
243
244        self.f = f
245        self.domain = domain
246
247
248    def get_conserved_quantities(self, volume=None, face=0):
249        return self.f(self.conserved_quantities,
250                      neighbour.conserved_quantities,
251                      self.domain.time)
252
253
254
255
256
257class File_boundary_old(Boundary):
258    """Boundary values obtained from file and interpolated.
259    conserved quantities as a function of time.
260
261    Assumes that file contains a time series.
262
263    No spatial info assumed.
264    """
265
266
267    def __init__(self, domain = None, filename = None):
268        import time
269        from Numeric import array
270        from config import time_format
271       
272        Boundary.__init__(self)
273
274
275        try:
276            fid = open(filename)
277        except Exception, e:
278            msg = 'Boundary file %s could not be opened: %s\n' %(filename, e)
279            raise msg
280
281
282        line = fid.readline()
283        fid.close()
284        fields = line.split(',')
285        msg = 'File %s must have the format date, values'
286        assert len(fields) == 2, msg
287
288        try:
289            starttime = time.mktime(time.strptime(fields[0], time_format))
290        except ValueError:   
291            msg = 'First field in file %s must be' %filename
292            msg += ' date-time with format %s.\n' %time_format
293            msg += 'I got %s instead.' %fields[0] 
294            raise msg
295
296        #Split values
297        values = []
298        for value in fields[1].split():
299            values.append(float(value))
300
301        q = array(values)
302       
303        msg = 'ERROR: File boundary function must return a 1d list or array '
304        assert len(q.shape) == 1, msg
305           
306        d = len(domain.conserved_quantities)
307        msg = 'Values specified in file must be a list or an array of length %d' %d
308        assert len(q) == d, msg
309
310        self.filename = filename
311        self.domain = domain
312        self.starttime = starttime
313
314        if domain.starttime is None:
315            domain.starttime = starttime
316        else:
317            msg = 'Start time as specified in domain (%s) is earlier '
318            'than the starttime of file %s: %s'\
319                  %(domain.starttime, self.filename, self.starttime) 
320            if self.starttime > domain.starttime:
321                raise msg
322       
323        self.read_time_boundary() #Now read all times in.
324       
325       
326    def read_time_boundary(self):
327        from Numeric import zeros, Float, alltrue
328        from config import time_format
329        import time
330       
331        fid = open(self.filename)       
332        lines = fid.readlines()
333        fid.close()
334       
335        N = len(lines)
336        d = len(self.domain.conserved_quantities)
337
338        T = zeros(N, Float)
339        Q = zeros((N, d), Float)
340
341       
342        for i, line in enumerate(lines):
343            fields = line.split(',')
344            real_time = time.mktime(time.strptime(fields[0], time_format))
345
346            T[i] = real_time - self.starttime
347
348           
349            for j, value in enumerate(fields[1].split()):
350                Q[i, j] = float(value)
351
352        msg = 'Time boundary must list time as a monotonuosly '
353        msg += 'increasing sequence'
354       
355        assert alltrue( T[1:] - T[:-1] > 0 ), msg
356
357        self.T = T     #Time
358        self.Q = Q     #Boundary values
359        self.index = 0 #Initial index
360
361       
362    def __repr__(self):
363        return 'File boundary'
364
365    def evaluate(self, vol_id=None, edge_id=None):
366        """Return linearly interpolated values based on domain.time
367        """
368
369        t = self.domain.time
370       
371        msg = 'Time given in File boundary does not match model time'
372        if t < self.T[0]: raise msg
373        if t > self.T[-1]: raise msg       
374
375        oldindex = self.index
376
377        #Find slot
378        while t > self.T[self.index]: self.index += 1
379        while t < self.T[self.index]: self.index -= 1
380
381        #if oldindex != self.index:
382        #    print 'Changing from %d to %d' %(oldindex, self.index)
383       
384       
385        #t is now between index and index+1
386        ratio = (t - self.T[self.index])/\
387                (self.T[self.index+1] - self.T[self.index])
388
389        return self.Q[self.index,:] +\
390               ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
391
392
393   
394   
395       
Note: See TracBrowser for help on using the repository browser.