Skip to content

Commit 0323e4b

Browse files
author
sveinlin
committed
added some files in softeng
1 parent 238bd39 commit 0323e4b

2 files changed

Lines changed: 727 additions & 0 deletions

File tree

src/softeng2/wave1D_oo.py

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Class implementation for solving of the wave equation
4+
u_tt = (c**2*u_x)_x + f(x,t) with t in [0,T] and x in (0,L).
5+
We have u=U_0 or du/dn=0 on x=0, and u=u_L or du/dn=0 on x = L.
6+
For simplicity, we use a constant c here and compare with a
7+
known exact solution.
8+
"""
9+
import time, glob, shutil, os
10+
import numpy as np
11+
12+
class Parameters(object):
13+
def __init__(self):
14+
"""
15+
Subclasses must initialize self.prm with
16+
parameters and default values, self.type with
17+
the corresponding types, and self.help with
18+
the corresponding descriptions of parameters.
19+
self.type and self.help are optional, but
20+
self.prms must be complete and contain all parameters.
21+
"""
22+
pass
23+
24+
def ok(self):
25+
"""Check if attr. prm, type, and help are defined."""
26+
if hasattr(self, 'prm') and \
27+
isinstance(self.prm, dict) and \
28+
hasattr(self, 'type') and \
29+
isinstance(self.type, dict) and \
30+
hasattr(self, 'help') and \
31+
isinstance(self.help, dict):
32+
return True
33+
else:
34+
raise ValueError(
35+
'The constructor in class %s does not '\
36+
'initialize the\ndictionaries '\
37+
'self.prm, self.type, self.help!' %
38+
self.__class__.__name__)
39+
40+
def _illegal_parameter(self, name):
41+
"""Raise exception about illegal parameter name."""
42+
raise ValueError(
43+
'parameter "%s" is not registered.\nLegal '\
44+
'parameters are\n%s' %
45+
(name, ' '.join(list(self.prm.keys()))))
46+
47+
def set(self, **parameters):
48+
"""Set one or more parameters."""
49+
for name in parameters:
50+
if name in self.prm:
51+
self.prm[name] = parameters[name]
52+
else:
53+
self._illegal_parameter(name)
54+
55+
def get(self, name):
56+
"""Get one or more parameter values."""
57+
if isinstance(name, (list,tuple)): # get many?
58+
for n in name:
59+
if n not in self.prm:
60+
self._illegal_parameter(name)
61+
return [self.prm[n] for n in name]
62+
else:
63+
if name not in self.prm:
64+
self._illegal_parameter(name)
65+
return self.prm[name]
66+
67+
def __getitem__(self, name):
68+
"""Allow obj[name] indexing to look up a parameter."""
69+
return self.get(name)
70+
71+
def __setitem__(self, name, value):
72+
"""
73+
Allow obj[name] = value syntax to assign a parameter's value.
74+
"""
75+
return self.set(name=value)
76+
77+
def define_command_line_options(self, parser=None):
78+
self.ok()
79+
if parser is None:
80+
import argparse
81+
parser = argparse.ArgumentParser()
82+
83+
for name in self.prm:
84+
tp = self.type[name] if name in self.type else str
85+
help = self.help[name] if name in self.help else None
86+
parser.add_argument(
87+
'--' + name, default=self.get(name), metavar=name,
88+
type=tp, help=help)
89+
90+
return parser
91+
92+
def init_from_command_line(self, args):
93+
for name in self.prm:
94+
self.prm[name] = getattr(args, name)
95+
96+
97+
class Problem(Parameters):
98+
"""
99+
Physical parameters for the wave equation
100+
u_tt = (c**2*u_x)_x + f(x,t) with t in [0,T] and
101+
x in (0,L). The problem definition is implied by
102+
the method of manufactured solution, choosing
103+
u(x,t)=x(L-x)(1+t/2) as our solution. This solution
104+
should be exactly reproduced when c is const.
105+
"""
106+
107+
def __init__(self):
108+
self.prm = dict(L=2.5, c=1.5, T=18)
109+
self.type = dict(L=float, c=float, T=float)
110+
self.help = dict(L='1D domain',
111+
c='coefficient (wave velocity) in PDE',
112+
T='end time of simulation')
113+
def u_exact(self, x, t):
114+
L = self['L']
115+
return x*(L-x)*(1+0.5*t)
116+
def I(self, x):
117+
return self.u_exact(x, 0)
118+
def V(self, x):
119+
return 0.5*self.u_exact(x, 0)
120+
def f(self, x, t):
121+
c = self['c']
122+
return 2*(1+0.5*t)*c**2
123+
def U_0(self, t):
124+
return self.u_exact(0, t)
125+
U_L = None
126+
127+
128+
class Solver(Parameters):
129+
"""
130+
Numerical parameters for solving the wave equation
131+
u_tt = (c**2*u_x)_x + f(x,t) with t in [0,T] and
132+
x in (0,L). The problem definition is implied by
133+
the method of manufactured solution, choosing
134+
u(x,t)=x(L-x)(1+t/2) as our solution. This solution
135+
should be exactly reproduced, provided c is const.
136+
We simulate in [0, L/2] and apply a symmetry condition
137+
at the end x=L/2.
138+
"""
139+
140+
def __init__(self, problem):
141+
self.problem = problem
142+
self.prm = dict(C = 0.75, Nx=3, stability_safety_factor=1.0)
143+
self.type = dict(C=float, Nx=int, stability_safety_factor=float)
144+
self.help = dict(C='Courant number',
145+
Nx='No of spatial mesh points',
146+
stability_safety_factor='stability factor')
147+
148+
from UniformFDMesh import Mesh, Function
149+
# introduce some local help variables to ease reading
150+
L_end = self.problem['L']
151+
dx = (L_end/2)/float(self['Nx'])
152+
t_interval = self.problem['T']
153+
dt = dx*self['stability_safety_factor']*self['C']/ \
154+
float(self.problem['c'])
155+
self.m = Mesh(L=[0,L_end/2],
156+
d=[dx],
157+
Nt = int(round(t_interval/float(dt))),
158+
T=t_interval)
159+
# The mesh function f will, after solving, contain
160+
# the solution for the whole domain and all time steps.
161+
self.f = Function(self.m, num_comp=1, space_only=False)
162+
163+
def solve(self, user_action=None, version='scalar'):
164+
# ...use local variables to ease reading
165+
L, c, T = self.problem['L c T'.split()]
166+
L = L/2 # compute with half the domain only (symmetry)
167+
C, Nx, stability_safety_factor = self[
168+
'C Nx stability_safety_factor'.split()]
169+
dx = self.m.d[0]
170+
I = self.problem.I
171+
V = self.problem.V
172+
f = self.problem.f
173+
U_0 = self.problem.U_0
174+
U_L = self.problem.U_L
175+
Nt = self.m.Nt
176+
t = np.linspace(0, T, Nt+1) # Mesh points in time
177+
x = np.linspace(0, L, Nx+1) # Mesh points in space
178+
179+
# Make sure dx and dt are compatible with x and t
180+
dx = x[1] - x[0]
181+
dt = t[1] - t[0]
182+
183+
# Treat c(x) as array
184+
if isinstance(c, (float,int)):
185+
c = np.zeros(x.shape) + c
186+
elif callable(c):
187+
# Call c(x) and fill array c
188+
c_ = np.zeros(x.shape)
189+
for i in range(Nx+1):
190+
c_[i] = c(x[i])
191+
c = c_
192+
193+
q = c**2
194+
C2 = (dt/dx)**2; dt2 = dt*dt # Help variables in the scheme
195+
196+
# Wrap user-given f, I, V, U_0, U_L if None or 0
197+
if f is None or f == 0:
198+
f = (lambda x, t: 0) if version == 'scalar' else \
199+
lambda x, t: np.zeros(x.shape)
200+
if I is None or I == 0:
201+
I = (lambda x: 0) if version == 'scalar' else \
202+
lambda x: np.zeros(x.shape)
203+
if V is None or V == 0:
204+
V = (lambda x: 0) if version == 'scalar' else \
205+
lambda x: np.zeros(x.shape)
206+
if U_0 is not None:
207+
if isinstance(U_0, (float,int)) and U_0 == 0:
208+
U_0 = lambda t: 0
209+
if U_L is not None:
210+
if isinstance(U_L, (float,int)) and U_L == 0:
211+
U_L = lambda t: 0
212+
213+
# Make hash of all input data
214+
import hashlib, inspect
215+
data = inspect.getsource(I) + '_' + inspect.getsource(V) + \
216+
'_' + inspect.getsource(f) + '_' + str(c) + '_' + \
217+
('None' if U_0 is None else inspect.getsource(U_0)) + \
218+
('None' if U_L is None else inspect.getsource(U_L)) + \
219+
'_' + str(L) + str(dt) + '_' + str(C) + '_' + str(T) + \
220+
'_' + str(stability_safety_factor)
221+
hashed_input = hashlib.sha1(data).hexdigest()
222+
if os.path.isfile('.' + hashed_input + '_archive.npz'):
223+
# Simulation is already run
224+
return -1, hashed_input
225+
226+
# use local variables to make code closer to mathematical
227+
# notation in computational scheme
228+
u_1 = self.f.u[0,:]
229+
u = self.f.u[1,:]
230+
231+
import time; t0 = time.clock() # CPU time measurement
232+
233+
Ix = range(0, Nx+1)
234+
It = range(0, Nt+1)
235+
236+
# Load initial condition into u_1
237+
for i in range(0,Nx+1):
238+
u_1[i] = I(x[i])
239+
240+
if user_action is not None:
241+
user_action(u_1, x, t, 0)
242+
243+
# Special formula for the first step
244+
for i in Ix[1:-1]:
245+
u[i] = u_1[i] + dt*V(x[i]) + \
246+
0.5*C2*(0.5*(q[i] + q[i+1])*(u_1[i+1] - u_1[i]) - \
247+
0.5*(q[i] + q[i-1])*(u_1[i] - u_1[i-1])) + \
248+
0.5*dt2*f(x[i], t[0])
249+
250+
i = Ix[0]
251+
if U_0 is None:
252+
# Set boundary values (x=0: i-1 -> i+1 since u[i-1]=u[i+1]
253+
# when du/dn = 0, on x=L: i+1 -> i-1 since u[i+1]=u[i-1])
254+
ip1 = i+1
255+
im1 = ip1 # i-1 -> i+1
256+
u[i] = u_1[i] + dt*V(x[i]) + \
257+
0.5*C2*(0.5*(q[i] + q[ip1])*(u_1[ip1] - u_1[i]) - \
258+
0.5*(q[i] + q[im1])*(u_1[i] - u_1[im1])) + \
259+
0.5*dt2*f(x[i], t[0])
260+
else:
261+
u[i] = U_0(dt)
262+
263+
i = Ix[-1]
264+
if U_L is None:
265+
im1 = i-1
266+
ip1 = im1 # i+1 -> i-1
267+
u[i] = u_1[i] + dt*V(x[i]) + \
268+
0.5*C2*(0.5*(q[i] + q[ip1])*(u_1[ip1] - u_1[i]) - \
269+
0.5*(q[i] + q[im1])*(u_1[i] - u_1[im1])) + \
270+
0.5*dt2*f(x[i], t[0])
271+
else:
272+
u[i] = U_L(dt)
273+
274+
if user_action is not None:
275+
user_action(u, x, t, 1)
276+
277+
for n in It[1:-1]:
278+
# u corresponds to u^{n+1} in the mathematical scheme
279+
u_2 = self.f.u[n-1,:]
280+
u_1 = self.f.u[n,:]
281+
u = self.f.u[n+1,:]
282+
283+
# Update all inner points
284+
if version == 'scalar':
285+
for i in Ix[1:-1]:
286+
u[i] = - u_2[i] + 2*u_1[i] + \
287+
C2*(0.5*(q[i] + q[i+1])*(u_1[i+1] - u_1[i]) - \
288+
0.5*(q[i] + q[i-1])*(u_1[i] - u_1[i-1])) + \
289+
dt2*f(x[i], t[n])
290+
291+
elif version == 'vectorized':
292+
u[1:-1] = - u_2[1:-1] + 2*u_1[1:-1] + \
293+
C2*(0.5*(q[1:-1] + q[2:])*(u_1[2:] - u_1[1:-1]) -
294+
0.5*(q[1:-1] + q[:-2])*(u_1[1:-1] - u_1[:-2])) + \
295+
dt2*f(x[1:-1], t[n])
296+
else:
297+
raise ValueError('version=%s' % version)
298+
299+
# Insert boundary conditions
300+
i = Ix[0]
301+
if U_0 is None:
302+
# Set boundary values
303+
# x=0: i-1 -> i+1 since u[i-1]=u[i+1] when du/dn=0
304+
# x=L: i+1 -> i-1 since u[i+1]=u[i-1] when du/dn=0
305+
ip1 = i+1
306+
im1 = ip1
307+
u[i] = - u_2[i] + 2*u_1[i] + \
308+
C2*(0.5*(q[i] + q[ip1])*(u_1[ip1] - u_1[i]) - \
309+
0.5*(q[i] + q[im1])*(u_1[i] - u_1[im1])) + \
310+
dt2*f(x[i], t[n])
311+
else:
312+
u[i] = U_0(t[n+1])
313+
314+
i = Ix[-1]
315+
if U_L is None:
316+
im1 = i-1
317+
ip1 = im1
318+
u[i] = - u_2[i] + 2*u_1[i] + \
319+
C2*(0.5*(q[i] + q[ip1])*(u_1[ip1] - u_1[i]) - \
320+
0.5*(q[i] + q[im1])*(u_1[i] - u_1[im1])) + \
321+
dt2*f(x[i], t[n])
322+
else:
323+
u[i] = U_L(t[n+1])
324+
325+
if user_action is not None:
326+
if user_action(u, x, t, n+1):
327+
break
328+
329+
cpu_time = time.clock() - t0
330+
return cpu_time, hashed_input
331+
332+
def assert_no_error(self):
333+
"""Run through mesh and check error"""
334+
Nx = self['Nx']
335+
Nt = self.m.Nt
336+
L, T = self.problem['L T'.split()]
337+
L = L/2 # only half the domain used (symmetry)
338+
x = np.linspace(0, L, Nx+1) # Mesh points in space
339+
t = np.linspace(0, T, Nt+1) # Mesh points in time
340+
341+
for n in range(len(t)):
342+
u_e = self.problem.u_exact(x, t[n])
343+
diff = np.abs(self.f.u[n,:] - u_e).max()
344+
print 'diff:', diff
345+
tol = 1E-13
346+
assert diff < tol
347+
348+
def test_quadratic_with_classes():
349+
"""
350+
Check the scalar and vectorized versions for a quadratic
351+
u(x,t)=x(L-x)(1+t/2) that is exactly reproduced,
352+
provided c(x) is constant. We simulate in [0, L/2] and
353+
apply a symmetry condition at the end x=L/2.
354+
"""
355+
356+
problem = Problem()
357+
solver = Solver(problem)
358+
359+
# Read input from the command line
360+
parser = problem.define_command_line_options()
361+
parser = solver. define_command_line_options(parser)
362+
args = parser.parse_args()
363+
problem.init_from_command_line(args)
364+
solver. init_from_command_line(args)
365+
366+
print parser.parse_args() # parameters ok?
367+
368+
solver.solve()
369+
print 'Check error.........................'
370+
solver.assert_no_error()
371+
372+
373+
if __name__ == '__main__':
374+
test_quadratic_with_classes()

0 commit comments

Comments
 (0)