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

Last change on this file since 596 was 566, checked in by ole, 20 years ago

First stab at wind stress from file

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