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

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

Work on file boundary (cyclone)

File size: 13.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_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
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
162class File_boundary(Boundary):
163    """Boundary values obtained from file and interpolated.
164    conserved quantities as a function of time.
165
166    Assumes that file contains a time series and possibly
167    also spatial info.
168    See docstring for File_function in util.py for details about
169    admissible file formats
170
171    The full solution history is not exactly the same as if
172    the models were couple:
173    File boundary must read and interpolate from *smoothed* version
174    as stored in sww and cannot work with the discontinuos triangles.
175   
176    """
177
178    def __init__(self, filename, domain, verbose = False):
179        import time
180        from Numeric import array, zeros, Float
181        from config import time_format
182        from util import file_function
183       
184        Boundary.__init__(self)
185
186        #Get x,y vertex coordinates for all triangles
187        V = domain.vertex_coordinates
188       
189        #Compute midpoint coordinates for all boundary elements
190        #Only a subset may be invoked when boundary is evaluated but
191        #we don't know which ones at this stage since this object can be attached to
192        #any tagged boundary later on.
193
194        if verbose: print 'Find midpoint coordinates of entire boundary'
195        self.midpoint_coordinates = zeros( (len(domain.boundary), 2), Float)
196        boundary_keys = domain.boundary.keys()
197       
198        #Record ordering #FIXME: should this happen in domain.py
199        self.boundary_indices = {} 
200       
201        for i, (vol_id, edge_id) in enumerate(boundary_keys):
202           
203            x0 = V[vol_id, 0]; y0 = V[vol_id, 1]
204            x1 = V[vol_id, 2]; y1 = V[vol_id, 3]
205            x2 = V[vol_id, 4]; y2 = V[vol_id, 5]           
206
207            #Midpoints       
208            if edge_id == 0: m = array([(x1 + x2)/2, (y1 + y2)/2])
209            if edge_id == 1: m = array([(x0 + x2)/2, (y0 + y2)/2])
210            if edge_id == 2: m = array([(x1 + x0)/2, (y1 + y0)/2])           
211            self.midpoint_coordinates[i,:] = m
212           
213            self.boundary_indices[(vol_id, edge_id)] = i
214       
215
216        if verbose: print 'Initialise file_function'
217        self.F = file_function(filename, domain, 
218                               interpolation_points=self.midpoint_coordinates,
219                               verbose = verbose)
220        self.domain = domain
221
222        #Test
223        q = self.F(0, point_id=0)
224
225        d = len(domain.conserved_quantities)
226        msg = 'Values specified in file %s must be ' %filename
227        msg += ' a list or an array of length %d' %d
228        assert len(q) == d, msg
229
230       
231    def __repr__(self):
232        return 'File boundary'
233
234
235    def evaluate(self, vol_id=None, edge_id=None):
236        """Return linearly interpolated values based on domain.time
237
238        vol_id and edge_id are ignored
239        """
240
241        t = self.domain.time
242
243        if vol_id is not None and edge_id is not None:
244            i = self.boundary_indices[ vol_id, edge_id ]
245            return self.F(t, point_id = i)
246        else:
247            #raise 'Boundary call without point_id not implemented'
248            #FIXME: What should the semantics be?       
249            return self.F(t)
250
251
252
253
254
255
256
257
258#THIS FAR (10/8/4)
259class Connective_boundary(Boundary):
260    """Connective boundary returns values for the
261    conserved quantities from a volume as defined by a connection table
262    mapping between tuples of (volume id, face id) for volumes that
263    have their boundaries connected.
264
265    FIXME: Perhaps include possibility of mapping between
266    different domains as well
267
268    FIXME: In case of shallow water we may want to have a
269    special version that casts this in terms of height rather than stage
270    """
271
272
273    def __init__(self, table):
274        from domain import Volume
275       
276        Boundary.__init__(self)
277
278        self.connection_table = table
279        self.Volume = Volume
280
281    def __repr__(self):
282        return 'Connective boundary'       
283
284    #FIXME: IF we ever need to get field_values from connected volume,
285    #that method could be overridden here (using same idea as in
286    #get_conserved_quantities
287    #def get_field_values()
288   
289    def get_conserved_quantities(self, volume, face=0):
290
291        id = volume.id
292        if self.connection_table.has_key((id, face)):
293            other_id, other_face = self.connection_table[(id, face)] 
294           
295            other_volume = self.Volume.instances[other_id]
296            cmd = 'q = other_volume.conserved_quantities_face%d' %face;
297            exec(cmd)       
298            return q
299        else:
300            msg = 'Volume, face tuple ($d, %d) has not been mapped'\
301                  %(id, face)
302            raise msg
303           
304       
305
306
307
308#FIXME: Add a boundary with a general function of x,y, and t
309
310#FIXME: Add periodic boundaries e.g.:
311# Attempt at periodic conditions from advection_spik. Remember this
312#
313#first = 2*(N-1)*N
314#for i in range(1,2*N+1,2):
315#    k = first + i-1#
316#
317#    print i,k
318#   
319#    domain[i].faces[2].neighbour = domain[k].faces[1]
320#    domain[k].faces[1].neighbour = domain[i].faces[2]   
321   
322
323
324class General_boundary(Boundary):
325    """General boundary which can compute conserved quantities based on
326    their previous value, conserved quantities of its neighbour and model time.
327
328    Must specify initial conserved quantities,
329    neighbour,
330    domain to get access to model time
331    a function f(q_old, neighbours_q, t) which must return
332    new conserved quantities q as a function time
333
334    FIXME: COMPLETE UNTESTED - JUST AN IDEA
335    """
336
337    def __init__(self, neighbour=None, conserved_quantities=None, domain=None, f=None):
338        Boundary.__init__(self, neighbour=neighbour, conserved_quantities=conserved_quantities)
339
340        self.f = f
341        self.domain = domain
342
343
344    def get_conserved_quantities(self, volume=None, face=0):
345        return self.f(self.conserved_quantities,
346                      neighbour.conserved_quantities,
347                      self.domain.time)
348
349
350
351
352
353class File_boundary_old(Boundary):
354    """Boundary values obtained from file and interpolated.
355    conserved quantities as a function of time.
356
357    Assumes that file contains a time series.
358
359    No spatial info assumed.
360    """
361
362
363    def __init__(self, domain = None, filename = None):
364        import time
365        from Numeric import array
366        from config import time_format
367       
368        Boundary.__init__(self)
369
370
371        try:
372            fid = open(filename)
373        except Exception, e:
374            msg = 'Boundary file %s could not be opened: %s\n' %(filename, e)
375            raise msg
376
377
378        line = fid.readline()
379        fid.close()
380        fields = line.split(',')
381        msg = 'File %s must have the format date, values'
382        assert len(fields) == 2, msg
383
384        try:
385            starttime = time.mktime(time.strptime(fields[0], time_format))
386        except ValueError:   
387            msg = 'First field in file %s must be' %filename
388            msg += ' date-time with format %s.\n' %time_format
389            msg += 'I got %s instead.' %fields[0] 
390            raise msg
391
392        #Split values
393        values = []
394        for value in fields[1].split():
395            values.append(float(value))
396
397        q = array(values)
398       
399        msg = 'ERROR: File boundary function must return a 1d list or array '
400        assert len(q.shape) == 1, msg
401           
402        d = len(domain.conserved_quantities)
403        msg = 'Values specified in file must be a list or an array of length %d' %d
404        assert len(q) == d, msg
405
406        self.filename = filename
407        self.domain = domain
408        self.starttime = starttime
409
410        if domain.starttime is None:
411            domain.starttime = starttime
412        else:
413            msg = 'Start time as specified in domain (%s) is earlier '
414            'than the starttime of file %s: %s'\
415                  %(domain.starttime, self.filename, self.starttime) 
416            if self.starttime > domain.starttime:
417                raise msg
418       
419        self.read_time_boundary() #Now read all times in.
420       
421       
422    def read_time_boundary(self):
423        from Numeric import zeros, Float, alltrue
424        from config import time_format
425        import time
426       
427        fid = open(self.filename)       
428        lines = fid.readlines()
429        fid.close()
430       
431        N = len(lines)
432        d = len(self.domain.conserved_quantities)
433
434        T = zeros(N, Float)
435        Q = zeros((N, d), Float)
436
437       
438        for i, line in enumerate(lines):
439            fields = line.split(',')
440            real_time = time.mktime(time.strptime(fields[0], time_format))
441
442            T[i] = real_time - self.starttime
443
444           
445            for j, value in enumerate(fields[1].split()):
446                Q[i, j] = float(value)
447
448        msg = 'Time boundary must list time as a monotonuosly '
449        msg += 'increasing sequence'
450       
451        assert alltrue( T[1:] - T[:-1] > 0 ), msg
452
453        self.T = T     #Time
454        self.Q = Q     #Boundary values
455        self.index = 0 #Initial index
456
457       
458    def __repr__(self):
459        return 'File boundary'
460
461    def evaluate(self, vol_id=None, edge_id=None):
462        """Return linearly interpolated values based on domain.time
463        """
464
465        t = self.domain.time
466       
467        msg = 'Time given in File boundary does not match model time'
468        if t < self.T[0]: raise msg
469        if t > self.T[-1]: raise msg       
470
471        oldindex = self.index
472
473        #Find slot
474        while t > self.T[self.index]: self.index += 1
475        while t < self.T[self.index]: self.index -= 1
476
477        #if oldindex != self.index:
478        #    print 'Changing from %d to %d' %(oldindex, self.index)
479       
480       
481        #t is now between index and index+1
482        ratio = (t - self.T[self.index])/\
483                (self.T[self.index+1] - self.T[self.index])
484
485        return self.Q[self.index,:] +\
486               ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
487
488
489   
490   
491       
Note: See TracBrowser for help on using the repository browser.