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

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

Fiddle

File size: 13.8 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            return self.F(t) #FIXME: What should the semantics be?
248
249
250
251
252
253
254
255
256#THIS FAR (10/8/4)
257class Connective_boundary(Boundary):
258    """Connective boundary returns values for the
259    conserved quantities from a volume as defined by a connection table
260    mapping between tuples of (volume id, face id) for volumes that
261    have their boundaries connected.
262
263    FIXME: Perhaps include possibility of mapping between
264    different domains as well
265
266    FIXME: In case of shallow water we may want to have a
267    special version that casts this in terms of height rather than stage
268    """
269
270
271    def __init__(self, table):
272        from domain import Volume
273       
274        Boundary.__init__(self)
275
276        self.connection_table = table
277        self.Volume = Volume
278
279    def __repr__(self):
280        return 'Connective boundary'       
281
282    #FIXME: IF we ever need to get field_values from connected volume,
283    #that method could be overridden here (using same idea as in
284    #get_conserved_quantities
285    #def get_field_values()
286   
287    def get_conserved_quantities(self, volume, face=0):
288
289        id = volume.id
290        if self.connection_table.has_key((id, face)):
291            other_id, other_face = self.connection_table[(id, face)] 
292           
293            other_volume = self.Volume.instances[other_id]
294            cmd = 'q = other_volume.conserved_quantities_face%d' %face;
295            exec(cmd)       
296            return q
297        else:
298            msg = 'Volume, face tuple ($d, %d) has not been mapped'\
299                  %(id, face)
300            raise msg
301           
302       
303
304
305
306#FIXME: Add a boundary with a general function of x,y, and t
307
308#FIXME: Add periodic boundaries e.g.:
309# Attempt at periodic conditions from advection_spik. Remember this
310#
311#first = 2*(N-1)*N
312#for i in range(1,2*N+1,2):
313#    k = first + i-1#
314#
315#    print i,k
316#   
317#    domain[i].faces[2].neighbour = domain[k].faces[1]
318#    domain[k].faces[1].neighbour = domain[i].faces[2]   
319   
320
321
322class General_boundary(Boundary):
323    """General boundary which can compute conserved quantities based on
324    their previous value, conserved quantities of its neighbour and model time.
325
326    Must specify initial conserved quantities,
327    neighbour,
328    domain to get access to model time
329    a function f(q_old, neighbours_q, t) which must return
330    new conserved quantities q as a function time
331
332    FIXME: COMPLETE UNTESTED - JUST AN IDEA
333    """
334
335    def __init__(self, neighbour=None, conserved_quantities=None, domain=None, f=None):
336        Boundary.__init__(self, neighbour=neighbour, conserved_quantities=conserved_quantities)
337
338        self.f = f
339        self.domain = domain
340
341
342    def get_conserved_quantities(self, volume=None, face=0):
343        return self.f(self.conserved_quantities,
344                      neighbour.conserved_quantities,
345                      self.domain.time)
346
347
348
349
350
351class File_boundary_old(Boundary):
352    """Boundary values obtained from file and interpolated.
353    conserved quantities as a function of time.
354
355    Assumes that file contains a time series.
356
357    No spatial info assumed.
358    """
359
360
361    def __init__(self, domain = None, filename = None):
362        import time
363        from Numeric import array
364        from config import time_format
365       
366        Boundary.__init__(self)
367
368
369        try:
370            fid = open(filename)
371        except Exception, e:
372            msg = 'Boundary file %s could not be opened: %s\n' %(filename, e)
373            raise msg
374
375
376        line = fid.readline()
377        fid.close()
378        fields = line.split(',')
379        msg = 'File %s must have the format date, values'
380        assert len(fields) == 2, msg
381
382        try:
383            starttime = time.mktime(time.strptime(fields[0], time_format))
384        except ValueError:   
385            msg = 'First field in file %s must be' %filename
386            msg += ' date-time with format %s.\n' %time_format
387            msg += 'I got %s instead.' %fields[0] 
388            raise msg
389
390        #Split values
391        values = []
392        for value in fields[1].split():
393            values.append(float(value))
394
395        q = array(values)
396       
397        msg = 'ERROR: File boundary function must return a 1d list or array '
398        assert len(q.shape) == 1, msg
399           
400        d = len(domain.conserved_quantities)
401        msg = 'Values specified in file must be a list or an array of length %d' %d
402        assert len(q) == d, msg
403
404        self.filename = filename
405        self.domain = domain
406        self.starttime = starttime
407
408        if domain.starttime is None:
409            domain.starttime = starttime
410        else:
411            msg = 'Start time as specified in domain (%s) is earlier '
412            'than the starttime of file %s: %s'\
413                  %(domain.starttime, self.filename, self.starttime) 
414            if self.starttime > domain.starttime:
415                raise msg
416       
417        self.read_time_boundary() #Now read all times in.
418       
419       
420    def read_time_boundary(self):
421        from Numeric import zeros, Float, alltrue
422        from config import time_format
423        import time
424       
425        fid = open(self.filename)       
426        lines = fid.readlines()
427        fid.close()
428       
429        N = len(lines)
430        d = len(self.domain.conserved_quantities)
431
432        T = zeros(N, Float)
433        Q = zeros((N, d), Float)
434
435       
436        for i, line in enumerate(lines):
437            fields = line.split(',')
438            real_time = time.mktime(time.strptime(fields[0], time_format))
439
440            T[i] = real_time - self.starttime
441
442           
443            for j, value in enumerate(fields[1].split()):
444                Q[i, j] = float(value)
445
446        msg = 'Time boundary must list time as a monotonuosly '
447        msg += 'increasing sequence'
448       
449        assert alltrue( T[1:] - T[:-1] > 0 ), msg
450
451        self.T = T     #Time
452        self.Q = Q     #Boundary values
453        self.index = 0 #Initial index
454
455       
456    def __repr__(self):
457        return 'File boundary'
458
459    def evaluate(self, vol_id=None, edge_id=None):
460        """Return linearly interpolated values based on domain.time
461        """
462
463        t = self.domain.time
464       
465        msg = 'Time given in File boundary does not match model time'
466        if t < self.T[0]: raise msg
467        if t > self.T[-1]: raise msg       
468
469        oldindex = self.index
470
471        #Find slot
472        while t > self.T[self.index]: self.index += 1
473        while t < self.T[self.index]: self.index -= 1
474
475        #if oldindex != self.index:
476        #    print 'Changing from %d to %d' %(oldindex, self.index)
477       
478       
479        #t is now between index and index+1
480        ratio = (t - self.T[self.index])/\
481                (self.T[self.index+1] - self.T[self.index])
482
483        return self.Q[self.index,:] +\
484               ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
485
486
487   
488   
489       
Note: See TracBrowser for help on using the repository browser.