1
+ """
2
+ Use the Adams-Bashforth methods to solve Ordinary Differential Equations.
3
+
4
+
5
+ https://en.wikipedia.org/wiki/Linear_multistep_method
6
+ Author : Ravi Kumar
7
+ """
8
+ from collections .abc import Callable
9
+
10
+ import numpy as np
11
+
12
+ class AdamsBashforth :
13
+ def __init__ (self , func : Callable [[float , float ], float ], x_initials : list [float ], y_initials : list [float ], step_size : float , x_final : float ):
14
+
15
+ if x_initials [- 1 ] >= x_final :
16
+ raise ValueError ("The final value of x must be greater than the initial values of x." )
17
+
18
+ if step_size <= 0 :
19
+ raise ValueError ("Step size must be positive." )
20
+
21
+ if not all (x1 - x0 == step_size for x0 , x1 in zip (x_initials , x_initials [1 :])):
22
+ raise ValueError ("x-values must be equally spaced according to step size." )
23
+
24
+ self .func = func
25
+ self .x_initials = x_initials
26
+ self .y_initials = y_initials
27
+ self .step_size = step_size
28
+ self .x_final = x_final
29
+
30
+ """
31
+ args:
32
+ func: An ordinary differential equation (ODE) as function of x and y.
33
+ x_initials: List containing initial required values of x.
34
+ y_initials: List containing initial required values of y.
35
+ step_size: The increment value of x.
36
+ x_final: The final value of x.
37
+
38
+ Returns: Solution of y at each nodal point
39
+
40
+ >>> def f(x, y):
41
+ ... return x
42
+ >>> y = AdamsBashforth(f, [0, 0.2], [0, 0], 0.2, 1).step_2()
43
+ >>> y
44
+ array([0. 0. 0.06 0.178 0.3654 0.63722])
45
+
46
+ >>> def f(x,y):
47
+ ... return x + y
48
+ >>> y = AdamsBashforth(f, [0, 0.2, 0.4, 0.6], [0, 0, 0.04, 0.128], 0.2, 1).step_4()
49
+ >>> y[-1]
50
+ 0.57710833
51
+
52
+ >>> def f(x, y):
53
+ ... return x + y
54
+ >>> y = AdamsBashforth(f, [0, 0.2, 1], [0, 0, 0.04], 0.2, 1).step_3()
55
+ Traceback (most recent call last):
56
+ ...
57
+ ValueError: The final value of x must be greater than the all initial values of x.
58
+
59
+ >>> def f(x, y):
60
+ ... return x + y
61
+ >>> y = AdamsBashforth(f, [0, 0.2, 0.3], [0, 0, 0.04], 0.2, 1).step_3()
62
+ Traceback (most recent call last):
63
+ ...
64
+ ValueError: x-values must be equally spaced according to step size.
65
+
66
+ >>> def f(x, y):
67
+ ... return x
68
+ >>> y = AdamsBashforth(f, [0, 0.2, 0.4, 0.6, 0.8], [0, 0, 0.04, 0.128, 0.307], -0.2, 1).step_5()
69
+ Traceback (most recent call last):
70
+ ...
71
+ ValueError: Step size must be positive.
72
+
73
+ >>> def f(x, y):
74
+ ... return (x -y)/2
75
+ >>> y = AdamsBashforth(f, [0, 0.2, 0.4], [0, 0, 0.04], 0.2, 1).step_2()
76
+ Traceback (most recent call last):
77
+ ...
78
+ ValueError: Insufficient nodal points values information.
79
+ """
80
+
81
+ def step_2 (self ) -> np .ndarray :
82
+ if len (self .x_initials ) != 2 or len (self .y_initials ) != 2 :
83
+ raise ValueError ("Insufficient nodal points values information." )
84
+
85
+ x_0 , x_1 = self .x_initials [:2 ]
86
+ y_0 , y_1 = self .y_initials [:2 ]
87
+
88
+ n = int ((self .x_final - x_1 ) / self .step_size )
89
+ y = np .zeros (n + 2 )
90
+ y [0 ] = y_0
91
+ y [1 ] = y_1
92
+
93
+ for i in range (n ):
94
+ y [i + 2 ] = y [i + 1 ] + (self .step_size / 2 )* (3 * self .func (x_1 , y [i + 1 ]) - self .func (x_0 , y [i ]))
95
+ x_0 = x_1
96
+ x_1 = x_1 + self .step_size
97
+
98
+ return y
99
+
100
+ def step_3 (self ) -> np .ndarray :
101
+ if len (self .x_initials ) != 3 or len (self .y_initials ) != 3 :
102
+ raise ValueError ("Insufficient nodal points information." )
103
+
104
+ x_0 , x_1 , x_2 = self .x_initials [:3 ]
105
+ y_0 , y_1 , y_2 = self .y_initials [:3 ]
106
+
107
+ n = int ((self .x_final - x_2 ) / self .step_size )
108
+ y = np .zeros (n + 4 )
109
+ y [0 ] = y_0
110
+ y [1 ] = y_1
111
+ y [2 ] = y_2
112
+
113
+ for i in range (n + 1 ):
114
+ y [i + 3 ] = y [i + 2 ] + (self .step_size / 12 )* (23 * self .func (x_2 , y [i + 2 ]) - 16 * self .func (x_1 , y [i + 1 ]) + 5 * self .func (x_0 , y [i ]))
115
+ x_0 = x_1
116
+ x_1 = x_2
117
+ x_2 = x_2 + self .step_size
118
+
119
+ return y
120
+
121
+ def step_4 (self ) -> np .ndarray :
122
+ if len (self .x_initials ) != 4 or len (self .y_initials ) != 4 :
123
+ raise ValueError ("Insufficient nodal points information." )
124
+
125
+ x_0 , x_1 , x_2 , x_3 = self .x_initials [:4 ]
126
+ y_0 , y_1 , y_2 , y_3 = self .y_initials [:4 ]
127
+
128
+ n = int ((self .x_final - x_3 ) / self .step_size )
129
+ y = np .zeros (n + 4 )
130
+ y [0 ] = y_0
131
+ y [1 ] = y_1
132
+ y [2 ] = y_2
133
+ y [3 ] = y_3
134
+
135
+ for i in range (n ):
136
+ y [i + 4 ] = y [i + 3 ] + (self .step_size / 24 ) * (55 * self .func (x_3 , y [i + 3 ]) - 59 * self .func (x_2 , y [i + 2 ]) + 37 * self .func (x_1 , y [i + 1 ]) - 9 * self .func (x_0 , y [i ]))
137
+ x_0 = x_1
138
+ x_1 = x_2
139
+ x_2 = x_3
140
+ x_3 = x_3 + self .step_size
141
+
142
+ return y
143
+
144
+ def step_5 (self ) -> np .ndarray :
145
+ if len (self .x_initials ) != 5 or len (self .y_initials ) != 5 :
146
+ raise ValueError ("Insufficient nodal points information." )
147
+
148
+ x_0 , x_1 , x_2 , x_3 , x_4 = self .x_initials [:5 ]
149
+ y_0 , y_1 , y_2 , y_3 , y_4 = self .y_initials [:5 ]
150
+
151
+ n = int ((self .x_final - x_4 ) / self .step_size )
152
+ y = np .zeros (n + 6 )
153
+ y [0 ] = y_0
154
+ y [1 ] = y_1
155
+ y [2 ] = y_2
156
+ y [3 ] = y_3
157
+ y [4 ] = y_4
158
+
159
+ for i in range (n + 1 ):
160
+ y [i + 5 ] = y [i + 4 ] + (self .step_size / 720 ) * (1901 * self .func (x_4 , y [i + 4 ]) - 2774 * self .func (x_3 , y [i + 3 ]) - 2616 * self .func (x_2 , y [i + 2 ]) - 1274 * self .func (x_1 , y [i + 1 ]) + 251 * self .func (x_0 , y [i ]))
161
+ x_0 = x_1
162
+ x_1 = x_2
163
+ x_2 = x_3
164
+ x_3 = x_4
165
+ x_4 = x_4 + self .step_size
166
+
167
+ return y
168
+
169
+ if __name__ == "__main__" :
170
+ import doctest
171
+
172
+ doctest .testmod ()
0 commit comments