import numpy as np import sys dt = float(sys.argv[1]) U0 = 1 T = 4 n = int(T/dt) t = np.zeros(n+1) u = np.zeros(n+1) t[0] = 0 u[0] = U0 for k in range(n): t[k+1] = t[k] + dt u[k+1] = (1 + dt)*u[k] # plot u against t def ForwardEuler(f, U0, T, n): """Solve u'=f(u,t), u(0)=U0, with n steps until t=T.""" import numpy as np t = np.zeros(n+1) u = np.zeros(n+1) # u[k] is the solution at time t[k] u[0] = U0 t[0] = 0 dt = T/float(n) for k in range(n): t[k+1] = t[k] + dt u[k+1] = u[k] + dt*f(u[k], t[k]) return u, t def f(u, t): return u U0 = 1 T = 3 n = 30 u, t = ForwardEuler(f, U0, T, n) %matplotlib inline from scitools.std import plot, exp u_exact = exp(t) plot(t, u, 'r-', t, u_exact, 'b-', xlabel='t', ylabel='u', legend=('numerical', 'exact'), title="Solution of the ODE u'=u, u(0)=1") method = ForwardEuler(f, dt) method.set_initial_condition(U0, t0) u, t = method.solve(T) plot(t, u) import numpy as np class ForwardEuler_v1: def __init__(self, f, dt): self.f, self.dt = f, dt def set_initial_condition(self, U0): self.U0 = float(U0) class ForwardEuler_v1: ... def solve(self, T): """Compute solution for 0 <= t <= T.""" n = int(round(T/self.dt)) # no of intervals self.u = np.zeros(n+1) self.t = np.zeros(n+1) self.u[0] = float(self.U0) self.t[0] = float(0) for k in range(self.n): self.k = k self.t[k+1] = self.t[k] + self.dt self.u[k+1] = self.advance() return self.u, self.t def advance(self): """Advance the solution one time step.""" # Create local variables to get rid of "self." in # the numerical formula u, dt, f, k, t = self.u, self.dt, self.f, self.k, self.t unew = u[k] + dt*f(u[k], t[k]) return unew # Idea: drop dt in the constructor. # Let the user provide all time points (in solve). class ForwardEuler: def __init__(self, f): # test that f is a function if not callable(f): raise TypeError('f is %s, not a function' % type(f)) self.f = f def set_initial_condition(self, U0): self.U0 = float(U0) def solve(self, time_points): ... class ForwardEuler: ... def solve(self, time_points): """Compute u for t values in time_points list.""" self.t = np.asarray(time_points) self.u = np.zeros(len(time_points)) self.u[0] = self.U0 for k in range(len(self.t)-1): self.k = k self.u[k+1] = self.advance() return self.u, self.t def advance(self): """Advance the solution one time step.""" u, f, k, t = self.u, self.f, self.k, self.t dt = t[k+1] - t[k] unew = u[k] + dt*f(u[k], t[k]) return unew def test_ForwardEuler_against_linear_solution(): def f(u, t): return 0.2 + (u - h(t))**4 def h(t): return 0.2*t + 3 solver = ForwardEuler(f) solver.set_initial_condition(U0=3) dt = 0.4; T = 3; n = int(round(float(T)/dt)) time_points = np.linspace(0, T, n+1) u, t = solver.solve(time_points) u_exact = h(t) diff = np.abs(u_exact - u).max() tol = 1E-14 success = diff < tol assert success class Logistic: def __init__(self, alpha, R, U0): self.alpha, self.R, self.U0 = alpha, float(R), U0 def __call__(self, u, t): # f(u,t) return self.alpha*u*(1 - u/self.R) problem = Logistic(0.2, 1, 0.1) time_points = np.linspace(0, 40, 401) method = ForwardEuler(problem) method.set_initial_condition(problem.U0) u, t = method.solve(time_points) class ODESolver: def __init__(self, f): self.f = f def advance(self): """Advance solution one time step.""" raise NotImplementedError # implement in subclass def set_initial_condition(self, U0): self.U0 = float(U0) def solve(self, time_points): self.t = np.asarray(time_points) self.u = np.zeros(len(self.t)) # Assume that self.t[0] corresponds to self.U0 self.u[0] = self.U0 # Time loop for k in range(n-1): self.k = k self.u[k+1] = self.advance() return self.u, self.t def advance(self): raise NotImplemtedError # to be impl. in subclasses class ForwardEuler(ODESolver): def advance(self): u, f, k, t = self.u, self.f, self.k, self.t dt = t[k+1] - t[k] unew = u[k] + dt*f(u[k], t) return unew from ODESolver import ForwardEuler def test1(u, t): return u method = ForwardEuler(test1) method.set_initial_condition(U0=1) u, t = method.solve(time_points=np.linspace(0, 3, 31)) plot(t, u) class RungeKutta4(ODESolver): def advance(self): u, f, k, t = self.u, self.f, self.k, self.t dt = t[k+1] - t[k] dt2 = dt/2.0 K1 = dt*f(u[k], t) K2 = dt*f(u[k] + 0.5*K1, t + dt2) K3 = dt*f(u[k] + 0.5*K2, t + dt2) K4 = dt*f(u[k] + K3, t + dt) unew = u[k] + (1/6.0)*(K1 + 2*K2 + 2*K3 + K4) return unew from ODESolver import RungeKutta4 def test1(u, t): return u method = RungeKutta4(test1) method.set_initial_condition(U0=1) u, t = method.solve(time_points=np.linspace(0, 3, 31)) plot(t, u) def terminate(u, t, step_no): eps = 1.0E-6 # small number return abs(u[step_no,0]) < eps # close enough to zero? unew = u[k] + dt*f(u[k], t) class ODESolver: def __init__(self, f): # Wrap user's f in a new function that always # converts list/tuple to array (or let array be array) self.f = lambda u, t: np.asarray(f(u, t), float) def set_initial_condition(self, U0): if isinstance(U0, (float,int)): # scalar ODE self.neq = 1 # no of equations U0 = float(U0) else: # system of ODEs U0 = np.asarray(U0) self.neq = U0.size # no of equations self.U0 = U0 class ODESolver: ... def solve(self, time_points, terminate=None): if terminate is None: terminate = lambda u, t, step_no: False self.t = np.asarray(time_points) n = self.t.size if self.neq == 1: # scalar ODEs self.u = np.zeros(n) else: # systems of ODEs self.u = np.zeros((n,self.neq)) # Assume that self.t[0] corresponds to self.U0 self.u[0] = self.U0 # Time loop for k in range(n-1): self.k = k self.u[k+1] = self.advance() if terminate(self.u, self.t, self.k+1): break # terminate loop over k return self.u[:k+2], self.t[:k+2] def myf(u, t): # u is array with two components u[0] and u[1]: return [u[1], -beta*u[1]/m - k*u[0]/m] class MyF: def __init__(self, m, k, beta): self.m, self.k, self.beta = m, k, beta def __call__(self, u, t): m, k, beta = self.m, self.k, self.beta return [u[1], -beta*u[1]/m - k*u[0]/m] from ODESolver import ForwardEuler # initial condition: U0 = [1.0, 0] f = MyF(1.0, 1.0, 0.0) # u'' + u = 0 => u(t)=cos(t) solver = ForwardEuler(f) solver.set_initial_condition(U0) T = 4*pi; dt = pi/20; n = int(round(T/dt)) time_points = np.linspace(0, T, n+1) u, t = solver.solve(time_points) # u is an array of [u0,u1] arrays, plot all u0 values: u0_values = u[:,0] u0_exact = cos(t) plot(t, u0_values, 'r-', t, u0_exact, 'b-') def f(u, t): x, vx, y, vy = u g = 9.81 return [vx, 0, vy, -g] from ODESolver import ForwardEuler # t=0: prescribe x, y, vx, vy x = y = 0 # start at the origin v0 = 5; theta = 80*pi/180 # velocity magnitude and angle vx = v0*cos(theta) vy = v0*sin(theta) # Initial condition: U0 = [x, vx, y, vy] solver= ForwardEuler(f) solver.set_initial_condition(u0) time_points = np.linspace(0, 1.2, 101) u, t = solver.solve(time_points) # u is an array of [x,vx,y,vy] arrays, plot y vs x: x = u[:,0]; y = u[:,2] plot(x, y) class Problem: def __init__(self, alpha, R, U0, T): self.alpha, self.R, self.U0, self.T = alpha, R, U0, T def __call__(self, u, t): """Return f(u, t).""" return self.alpha*u*(1 - u/self.R(t)) def terminate(self, u, t, step_no): """Terminate when u is close to R.""" tol = self.R*0.01 return abs(u[step_no] - self.R) < tol problem = Problem(alpha=0.1, R=500, U0=2, T=130) class Solver: def __init__(self, problem, dt, method=ODESolver.ForwardEuler): self.problem, self.dt = problem, dt self.method = method def solve(self): solver = self.method(self.problem) solver.set_initial_condition(self.problem.U0) n = int(round(self.problem.T/self.dt)) t_points = np.linspace(0, self.problem.T, n+1) self.u, self.t = solver.solve(t_points, self.problem.terminate) def plot(self): plot(self.t, self.u) problem = Problem(alpha=0.1, U0=2, T=130, R=lambda t: 500 if t < 60 else 100) solver = Solver(problem, dt=1.) solver.solve() solver.plot() print 'max u:', solver.u.max()