Skip to content

Commit 444dfb0

Browse files
ravi-ivar-7pre-commit-ci[bot]cclauss
authored
Added adams-bashforth method of order 2, 3, 4, 5 (#10969)
* added runge kutta gills method * added adams-bashforth method of order 2, 3, 4, 5 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update adams_bashforth.py * Deleted extraneous file, maths/numerical_analysis/runge_kutta_gills.py * Added doctests to each function adams_bashforth.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update adams_bashforth.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com>
1 parent d80ee90 commit 444dfb0

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"""
2+
Use the Adams-Bashforth methods to solve Ordinary Differential Equations.
3+
4+
https://en.wikipedia.org/wiki/Linear_multistep_method
5+
Author : Ravi Kumar
6+
"""
7+
from collections.abc import Callable
8+
from dataclasses import dataclass
9+
10+
import numpy as np
11+
12+
13+
@dataclass
14+
class AdamsBashforth:
15+
"""
16+
args:
17+
func: An ordinary differential equation (ODE) as function of x and y.
18+
x_initials: List containing initial required values of x.
19+
y_initials: List containing initial required values of y.
20+
step_size: The increment value of x.
21+
x_final: The final value of x.
22+
23+
Returns: Solution of y at each nodal point
24+
25+
>>> def f(x, y):
26+
... return x + y
27+
>>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0.2, 1], 0.2, 1) # doctest: +ELLIPSIS
28+
AdamsBashforth(func=..., x_initials=[0, 0.2, 0.4], y_initials=[0, 0.2, 1], step...)
29+
>>> AdamsBashforth(f, [0, 0.2, 1], [0, 0, 0.04], 0.2, 1).step_2()
30+
Traceback (most recent call last):
31+
...
32+
ValueError: The final value of x must be greater than the initial values of x.
33+
34+
>>> AdamsBashforth(f, [0, 0.2, 0.3], [0, 0, 0.04], 0.2, 1).step_3()
35+
Traceback (most recent call last):
36+
...
37+
ValueError: x-values must be equally spaced according to step size.
38+
39+
>>> AdamsBashforth(f,[0,0.2,0.4,0.6,0.8],[0,0,0.04,0.128,0.307],-0.2,1).step_5()
40+
Traceback (most recent call last):
41+
...
42+
ValueError: Step size must be positive.
43+
"""
44+
45+
func: Callable[[float, float], float]
46+
x_initials: list[float]
47+
y_initials: list[float]
48+
step_size: float
49+
x_final: float
50+
51+
def __post_init__(self) -> None:
52+
if self.x_initials[-1] >= self.x_final:
53+
raise ValueError(
54+
"The final value of x must be greater than the initial values of x."
55+
)
56+
57+
if self.step_size <= 0:
58+
raise ValueError("Step size must be positive.")
59+
60+
if not all(
61+
round(x1 - x0, 10) == self.step_size
62+
for x0, x1 in zip(self.x_initials, self.x_initials[1:])
63+
):
64+
raise ValueError("x-values must be equally spaced according to step size.")
65+
66+
def step_2(self) -> np.ndarray:
67+
"""
68+
>>> def f(x, y):
69+
... return x
70+
>>> AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_2()
71+
array([0. , 0. , 0.06, 0.16, 0.3 , 0.48])
72+
73+
>>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_2()
74+
Traceback (most recent call last):
75+
...
76+
ValueError: Insufficient initial points information.
77+
"""
78+
79+
if len(self.x_initials) != 2 or len(self.y_initials) != 2:
80+
raise ValueError("Insufficient initial points information.")
81+
82+
x_0, x_1 = self.x_initials[:2]
83+
y_0, y_1 = self.y_initials[:2]
84+
85+
n = int((self.x_final - x_1) / self.step_size)
86+
y = np.zeros(n + 2)
87+
y[0] = y_0
88+
y[1] = y_1
89+
90+
for i in range(n):
91+
y[i + 2] = y[i + 1] + (self.step_size / 2) * (
92+
3 * self.func(x_1, y[i + 1]) - self.func(x_0, y[i])
93+
)
94+
x_0 = x_1
95+
x_1 += self.step_size
96+
97+
return y
98+
99+
def step_3(self) -> np.ndarray:
100+
"""
101+
>>> def f(x, y):
102+
... return x + y
103+
>>> y = AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_3()
104+
>>> y[3]
105+
0.15533333333333332
106+
107+
>>> AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_3()
108+
Traceback (most recent call last):
109+
...
110+
ValueError: Insufficient initial points information.
111+
"""
112+
if len(self.x_initials) != 3 or len(self.y_initials) != 3:
113+
raise ValueError("Insufficient initial points information.")
114+
115+
x_0, x_1, x_2 = self.x_initials[:3]
116+
y_0, y_1, y_2 = self.y_initials[:3]
117+
118+
n = int((self.x_final - x_2) / self.step_size)
119+
y = np.zeros(n + 4)
120+
y[0] = y_0
121+
y[1] = y_1
122+
y[2] = y_2
123+
124+
for i in range(n + 1):
125+
y[i + 3] = y[i + 2] + (self.step_size / 12) * (
126+
23 * self.func(x_2, y[i + 2])
127+
- 16 * self.func(x_1, y[i + 1])
128+
+ 5 * self.func(x_0, y[i])
129+
)
130+
x_0 = x_1
131+
x_1 = x_2
132+
x_2 += self.step_size
133+
134+
return y
135+
136+
def step_4(self) -> np.ndarray:
137+
"""
138+
>>> def f(x,y):
139+
... return x + y
140+
>>> y = AdamsBashforth(
141+
... f, [0, 0.2, 0.4, 0.6], [0, 0, 0.04, 0.128], 0.2, 1).step_4()
142+
>>> y[4]
143+
0.30699999999999994
144+
>>> y[5]
145+
0.5771083333333333
146+
147+
>>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_4()
148+
Traceback (most recent call last):
149+
...
150+
ValueError: Insufficient initial points information.
151+
"""
152+
153+
if len(self.x_initials) != 4 or len(self.y_initials) != 4:
154+
raise ValueError("Insufficient initial points information.")
155+
156+
x_0, x_1, x_2, x_3 = self.x_initials[:4]
157+
y_0, y_1, y_2, y_3 = self.y_initials[:4]
158+
159+
n = int((self.x_final - x_3) / self.step_size)
160+
y = np.zeros(n + 4)
161+
y[0] = y_0
162+
y[1] = y_1
163+
y[2] = y_2
164+
y[3] = y_3
165+
166+
for i in range(n):
167+
y[i + 4] = y[i + 3] + (self.step_size / 24) * (
168+
55 * self.func(x_3, y[i + 3])
169+
- 59 * self.func(x_2, y[i + 2])
170+
+ 37 * self.func(x_1, y[i + 1])
171+
- 9 * self.func(x_0, y[i])
172+
)
173+
x_0 = x_1
174+
x_1 = x_2
175+
x_2 = x_3
176+
x_3 += self.step_size
177+
178+
return y
179+
180+
def step_5(self) -> np.ndarray:
181+
"""
182+
>>> def f(x,y):
183+
... return x + y
184+
>>> y = AdamsBashforth(
185+
... f, [0, 0.2, 0.4, 0.6, 0.8], [0, 0.02140, 0.02140, 0.22211, 0.42536],
186+
... 0.2, 1).step_5()
187+
>>> y[-1]
188+
0.05436839444444452
189+
190+
>>> AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_5()
191+
Traceback (most recent call last):
192+
...
193+
ValueError: Insufficient initial points information.
194+
"""
195+
196+
if len(self.x_initials) != 5 or len(self.y_initials) != 5:
197+
raise ValueError("Insufficient initial points information.")
198+
199+
x_0, x_1, x_2, x_3, x_4 = self.x_initials[:5]
200+
y_0, y_1, y_2, y_3, y_4 = self.y_initials[:5]
201+
202+
n = int((self.x_final - x_4) / self.step_size)
203+
y = np.zeros(n + 6)
204+
y[0] = y_0
205+
y[1] = y_1
206+
y[2] = y_2
207+
y[3] = y_3
208+
y[4] = y_4
209+
210+
for i in range(n + 1):
211+
y[i + 5] = y[i + 4] + (self.step_size / 720) * (
212+
1901 * self.func(x_4, y[i + 4])
213+
- 2774 * self.func(x_3, y[i + 3])
214+
- 2616 * self.func(x_2, y[i + 2])
215+
- 1274 * self.func(x_1, y[i + 1])
216+
+ 251 * self.func(x_0, y[i])
217+
)
218+
x_0 = x_1
219+
x_1 = x_2
220+
x_2 = x_3
221+
x_3 = x_4
222+
x_4 += self.step_size
223+
224+
return y
225+
226+
227+
if __name__ == "__main__":
228+
import doctest
229+
230+
doctest.testmod()

0 commit comments

Comments
 (0)