Spaces:
Runtime error
Runtime error
modularized
Browse files- maths/differential_equations/differential_equations.py +0 -236
- maths/differential_equations/differential_equations_interface.py +3 -2
- maths/differential_equations/ode_examples.py +49 -0
- maths/differential_equations/solve_first_order_ode.py +62 -0
- maths/differential_equations/solve_second_order_ode.py +76 -0
maths/differential_equations/differential_equations.py
DELETED
@@ -1,236 +0,0 @@
|
|
1 |
-
"""
|
2 |
-
Differential Equations solvers for university level, using SciPy.
|
3 |
-
"""
|
4 |
-
import numpy as np
|
5 |
-
from scipy.integrate import solve_ivp
|
6 |
-
from typing import Callable, List, Tuple, Dict, Any, Union
|
7 |
-
import matplotlib.pyplot as plt
|
8 |
-
|
9 |
-
# Type hint for the function defining the ODE system
|
10 |
-
ODEFunc = Callable[[float, Union[np.ndarray, List[float]]], Union[np.ndarray, List[float]]]
|
11 |
-
|
12 |
-
def solve_first_order_ode(
|
13 |
-
ode_func: ODEFunc,
|
14 |
-
t_span: Tuple[float, float],
|
15 |
-
y0: List[float],
|
16 |
-
t_eval_count: int = 100,
|
17 |
-
method: str = 'RK45',
|
18 |
-
**kwargs: Any
|
19 |
-
) -> Dict[str, Union[np.ndarray, str, bool]]:
|
20 |
-
"""
|
21 |
-
Solves a first-order ordinary differential equation (or system of first-order ODEs)
|
22 |
-
dy/dt = f(t, y) with initial condition y(t0) = y0.
|
23 |
-
|
24 |
-
Args:
|
25 |
-
ode_func: Callable f(t, y). `t` is a scalar, `y` is an ndarray of shape (n,).
|
26 |
-
It should return an array-like object of shape (n,).
|
27 |
-
Example for dy/dt = -2*y: lambda t, y: -2 * y
|
28 |
-
Example for system dy1/dt = y2, dy2/dt = -y1: lambda t, y: [y[1], -y[0]]
|
29 |
-
t_span: Tuple (t_start, t_end) for the integration interval.
|
30 |
-
y0: List or NumPy array of initial conditions y(t_start).
|
31 |
-
t_eval_count: Number of points at which to store the computed solution.
|
32 |
-
method: Integration method to use (e.g., 'RK45', 'LSODA', 'BDF').
|
33 |
-
**kwargs: Additional keyword arguments to pass to `solve_ivp` (e.g., `rtol`, `atol`).
|
34 |
-
|
35 |
-
Returns:
|
36 |
-
A dictionary containing:
|
37 |
-
't': NumPy array of time points.
|
38 |
-
'y': NumPy array of solution values at each time point. (y[i] corresponds to t[i])
|
39 |
-
For systems, y is an array with n_equations rows and n_timepoints columns.
|
40 |
-
'message': Solver message.
|
41 |
-
'success': Boolean indicating if the solver was successful.
|
42 |
-
'plot_path': Path to a saved plot of the solution (if successful), else None.
|
43 |
-
"""
|
44 |
-
try:
|
45 |
-
y0_np = np.array(y0, dtype=float)
|
46 |
-
t_eval = np.linspace(t_span[0], t_span[1], t_eval_count)
|
47 |
-
|
48 |
-
sol = solve_ivp(ode_func, t_span, y0_np, method=method, t_eval=t_eval, **kwargs)
|
49 |
-
|
50 |
-
plot_path = None
|
51 |
-
if sol.success:
|
52 |
-
try:
|
53 |
-
plt.figure(figsize=(10, 6))
|
54 |
-
if y0_np.ndim == 0 or len(y0_np) == 1 : # Single equation
|
55 |
-
plt.plot(sol.t, sol.y[0], label=f'y(t), y0={y0_np[0] if y0_np.ndim > 0 else y0_np}')
|
56 |
-
else: # System of equations
|
57 |
-
for i in range(sol.y.shape[0]):
|
58 |
-
plt.plot(sol.t, sol.y[i], label=f'y_{i+1}(t), y0_{i+1}={y0_np[i]}')
|
59 |
-
plt.xlabel("Time (t)")
|
60 |
-
plt.ylabel("Solution y(t)")
|
61 |
-
plt.title(f"Solution of First-Order ODE ({method})")
|
62 |
-
plt.legend()
|
63 |
-
plt.grid(True)
|
64 |
-
plot_path = "ode_solution_plot.png"
|
65 |
-
plt.savefig(plot_path)
|
66 |
-
plt.close() # Close the plot to free memory
|
67 |
-
except Exception as e_plot:
|
68 |
-
print(f"Warning: Could not generate plot: {e_plot}")
|
69 |
-
plot_path = None
|
70 |
-
|
71 |
-
|
72 |
-
return {
|
73 |
-
't': sol.t,
|
74 |
-
'y': sol.y,
|
75 |
-
'message': sol.message,
|
76 |
-
'success': sol.success,
|
77 |
-
'plot_path': plot_path
|
78 |
-
}
|
79 |
-
except Exception as e:
|
80 |
-
return {
|
81 |
-
't': np.array([]),
|
82 |
-
'y': np.array([]),
|
83 |
-
'message': f"Error during ODE solving: {str(e)}",
|
84 |
-
'success': False,
|
85 |
-
'plot_path': None
|
86 |
-
}
|
87 |
-
|
88 |
-
def solve_second_order_ode(
|
89 |
-
ode_func_second_order: Callable[[float, float, float], float],
|
90 |
-
t_span: Tuple[float, float],
|
91 |
-
y0: float,
|
92 |
-
dy0_dt: float,
|
93 |
-
t_eval_count: int = 100,
|
94 |
-
method: str = 'RK45',
|
95 |
-
**kwargs: Any
|
96 |
-
) -> Dict[str, Union[np.ndarray, str, bool]]:
|
97 |
-
"""
|
98 |
-
Solves a single second-order ordinary differential equation of the form
|
99 |
-
d²y/dt² = f(t, y, dy/dt) with initial conditions y(t0)=y0 and dy/dt(t0)=dy0_dt.
|
100 |
-
|
101 |
-
This is done by converting the second-order ODE into a system of two first-order ODEs:
|
102 |
-
Let z1 = y and z2 = dy/dt.
|
103 |
-
Then dz1/dt = z2
|
104 |
-
And dz2/dt = d²y/dt² = f(t, z1, z2)
|
105 |
-
|
106 |
-
Args:
|
107 |
-
ode_func_second_order: Callable f(t, y, dy_dt). `t` is scalar, `y` is scalar, `dy_dt` is scalar.
|
108 |
-
It should return the value of d²y/dt².
|
109 |
-
Example for d²y/dt² = -0.1*(dy/dt) - y: lambda t, y, dy_dt: -0.1 * dy_dt - y
|
110 |
-
t_span: Tuple (t_start, t_end) for the integration interval.
|
111 |
-
y0: Initial value of y at t_start.
|
112 |
-
dy0_dt: Initial value of dy/dt at t_start.
|
113 |
-
t_eval_count: Number of points at which to store the computed solution.
|
114 |
-
method: Integration method to use.
|
115 |
-
**kwargs: Additional keyword arguments for `solve_ivp`.
|
116 |
-
|
117 |
-
Returns:
|
118 |
-
A dictionary containing:
|
119 |
-
't': NumPy array of time points.
|
120 |
-
'y': NumPy array of solution values y(t) at each time point.
|
121 |
-
'dy_dt': NumPy array of solution values dy/dt(t) at each time point.
|
122 |
-
'message': Solver message.
|
123 |
-
'success': Boolean indicating if the solver was successful.
|
124 |
-
'plot_path': Path to a saved plot of y(t) and dy/dt(t) (if successful), else None.
|
125 |
-
"""
|
126 |
-
# Define the system of first-order ODEs
|
127 |
-
def system_func(t: float, z: np.ndarray) -> List[float]:
|
128 |
-
y_val, dy_dt_val = z[0], z[1]
|
129 |
-
d2y_dt2_val = ode_func_second_order(t, y_val, dy_dt_val)
|
130 |
-
return [dy_dt_val, d2y_dt2_val]
|
131 |
-
|
132 |
-
initial_conditions_system = [y0, dy0_dt]
|
133 |
-
|
134 |
-
try:
|
135 |
-
t_eval = np.linspace(t_span[0], t_span[1], t_eval_count)
|
136 |
-
sol = solve_ivp(system_func, t_span, initial_conditions_system, method=method, t_eval=t_eval, **kwargs)
|
137 |
-
|
138 |
-
plot_path = None
|
139 |
-
if sol.success:
|
140 |
-
try:
|
141 |
-
plt.figure(figsize=(12, 7))
|
142 |
-
|
143 |
-
plt.subplot(2,1,1)
|
144 |
-
plt.plot(sol.t, sol.y[0], label=f'y(t), y0={y0}')
|
145 |
-
plt.xlabel("Time (t)")
|
146 |
-
plt.ylabel("y(t)")
|
147 |
-
plt.title(f"Solution of Second-Order ODE: y(t) ({method})")
|
148 |
-
plt.legend()
|
149 |
-
plt.grid(True)
|
150 |
-
|
151 |
-
plt.subplot(2,1,2)
|
152 |
-
plt.plot(sol.t, sol.y[1], label=f'dy/dt(t), dy0/dt={dy0_dt}', color='orange')
|
153 |
-
plt.xlabel("Time (t)")
|
154 |
-
plt.ylabel("dy/dt(t)")
|
155 |
-
plt.title(f"Solution of Second-Order ODE: dy/dt(t) ({method})")
|
156 |
-
plt.legend()
|
157 |
-
plt.grid(True)
|
158 |
-
|
159 |
-
plt.tight_layout()
|
160 |
-
plot_path = "ode_second_order_solution_plot.png"
|
161 |
-
plt.savefig(plot_path)
|
162 |
-
plt.close()
|
163 |
-
except Exception as e_plot:
|
164 |
-
print(f"Warning: Could not generate plot: {e_plot}")
|
165 |
-
plot_path = None
|
166 |
-
|
167 |
-
return {
|
168 |
-
't': sol.t,
|
169 |
-
'y': sol.y[0], # First component of the system's solution
|
170 |
-
'dy_dt': sol.y[1], # Second component of the system's solution
|
171 |
-
'message': sol.message,
|
172 |
-
'success': sol.success,
|
173 |
-
'plot_path': plot_path
|
174 |
-
}
|
175 |
-
except Exception as e:
|
176 |
-
return {
|
177 |
-
't': np.array([]),
|
178 |
-
'y': np.array([]),
|
179 |
-
'dy_dt': np.array([]),
|
180 |
-
'message': f"Error during ODE solving: {str(e)}",
|
181 |
-
'success': False,
|
182 |
-
'plot_path': None
|
183 |
-
}
|
184 |
-
|
185 |
-
# Example Usage (can be removed or commented out)
|
186 |
-
if __name__ == '__main__':
|
187 |
-
# --- First-order ODE example: dy/dt = -y*t with y(0)=1 ---
|
188 |
-
def first_order_example(t, y):
|
189 |
-
return -y * t
|
190 |
-
|
191 |
-
print("Solving dy/dt = -y*t, y(0)=1 from t=0 to t=5")
|
192 |
-
solution1 = solve_first_order_ode(first_order_example, (0, 5), [1], t_eval_count=50)
|
193 |
-
if solution1['success']:
|
194 |
-
print(f"First-order ODE solved. Message: {solution1['message']}")
|
195 |
-
# print("t:", solution1['t'])
|
196 |
-
# print("y:", solution1['y'])
|
197 |
-
if solution1['plot_path']:
|
198 |
-
print(f"Plot saved to {solution1['plot_path']}")
|
199 |
-
else:
|
200 |
-
print(f"First-order ODE failed. Message: {solution1['message']}")
|
201 |
-
|
202 |
-
# --- First-order system example: Lotka-Volterra ---
|
203 |
-
# dy1/dt = a*y1 - b*y1*y2 (prey)
|
204 |
-
# dy2/dt = c*y1*y2 - d*y2 (predator)
|
205 |
-
a, b, c, d = 1.5, 0.8, 0.5, 0.9
|
206 |
-
def lotka_volterra(t, y):
|
207 |
-
prey, predator = y[0], y[1]
|
208 |
-
d_prey_dt = a * prey - b * prey * predator
|
209 |
-
d_predator_dt = c * prey * predator - d * predator
|
210 |
-
return [d_prey_dt, d_predator_dt]
|
211 |
-
|
212 |
-
print("\nSolving Lotka-Volterra system from t=0 to t=20 with y0=[10, 5]")
|
213 |
-
solution_lv = solve_first_order_ode(lotka_volterra, (0, 20), [10, 5], t_eval_count=200)
|
214 |
-
if solution_lv['success']:
|
215 |
-
print(f"Lotka-Volterra solved. Plot saved to {solution_lv['plot_path']}")
|
216 |
-
else:
|
217 |
-
print(f"Lotka-Volterra failed. Message: {solution_lv['message']}")
|
218 |
-
|
219 |
-
|
220 |
-
# --- Second-order ODE example: d²y/dt² = -sin(y) (simple pendulum) ---
|
221 |
-
# y is theta, dy/dt is omega. d²y/dt² = -g/L * sin(y)
|
222 |
-
g_L = 9.81 / 1.0 # Example: g/L = 9.81
|
223 |
-
def pendulum_ode(t, y_angle, dy_dt_angular_velocity):
|
224 |
-
return -g_L * np.sin(y_angle)
|
225 |
-
|
226 |
-
print("\nSolving d²y/dt² = -g/L*sin(y), y(0)=pi/4, dy/dt(0)=0 from t=0 to t=10")
|
227 |
-
solution2 = solve_second_order_ode(pendulum_ode, (0, 10), y0=np.pi/4, dy0_dt=0, t_eval_count=100)
|
228 |
-
if solution2['success']:
|
229 |
-
print(f"Second-order ODE solved. Message: {solution2['message']}")
|
230 |
-
# print("t:", solution2['t'])
|
231 |
-
# print("y:", solution2['y'])
|
232 |
-
# print("dy/dt:", solution2['dy_dt'])
|
233 |
-
if solution2['plot_path']:
|
234 |
-
print(f"Plot saved to {solution2['plot_path']}")
|
235 |
-
else:
|
236 |
-
print(f"Second-order ODE failed. Message: {solution2['message']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
maths/differential_equations/differential_equations_interface.py
CHANGED
@@ -14,7 +14,8 @@ import ast # For safer string literal evaluation if needed for parameters
|
|
14 |
import math # To make math functions available in eval scope for ODEs
|
15 |
|
16 |
# Import the solver functions
|
17 |
-
from maths.differential_equations.
|
|
|
18 |
|
19 |
# --- Helper Functions ---
|
20 |
|
@@ -46,7 +47,7 @@ def string_to_ode_func(lambda_str: str, expected_args: Tuple[str, ...]) -> Calla
|
|
46 |
Includes a basic check for 'lambda' keyword and argument count.
|
47 |
|
48 |
Args:
|
49 |
-
lambda_str: The string, e.g., "lambda t, y: -
|
50 |
expected_args: A tuple of expected argument names, e.g., ('t', 'y') or ('t', 'y', 'dy_dt').
|
51 |
|
52 |
Returns:
|
|
|
14 |
import math # To make math functions available in eval scope for ODEs
|
15 |
|
16 |
# Import the solver functions
|
17 |
+
from maths.differential_equations.solve_first_order_ode import solve_first_order_ode
|
18 |
+
from maths.differential_equations.solve_second_order_ode import solve_second_order_ode
|
19 |
|
20 |
# --- Helper Functions ---
|
21 |
|
|
|
47 |
Includes a basic check for 'lambda' keyword and argument count.
|
48 |
|
49 |
Args:
|
50 |
+
lambda_str: The string, e.g., "lambda t, y: -y" or "lambda t, y, dy_dt: -0.1*dy_dt -y".
|
51 |
expected_args: A tuple of expected argument names, e.g., ('t', 'y') or ('t', 'y', 'dy_dt').
|
52 |
|
53 |
Returns:
|
maths/differential_equations/ode_examples.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Example usage for ODE solvers. Can be run as a script.
|
3 |
+
"""
|
4 |
+
import numpy as np
|
5 |
+
from solve_first_order_ode import solve_first_order_ode
|
6 |
+
from solve_second_order_ode import solve_second_order_ode
|
7 |
+
|
8 |
+
if __name__ == '__main__':
|
9 |
+
# --- First-order ODE example: dy/dt = -y*t with y(0)=1 ---
|
10 |
+
def first_order_example(t, y):
|
11 |
+
return -y * t
|
12 |
+
|
13 |
+
print("Solving dy/dt = -y*t, y(0)=1 from t=0 to t=5")
|
14 |
+
solution1 = solve_first_order_ode(first_order_example, (0, 5), [1], t_eval_count=50)
|
15 |
+
if solution1['success']:
|
16 |
+
print(f"First-order ODE solved. Message: {solution1['message']}")
|
17 |
+
if solution1['plot_path']:
|
18 |
+
print(f"Plot saved to {solution1['plot_path']}")
|
19 |
+
else:
|
20 |
+
print(f"First-order ODE failed. Message: {solution1['message']}")
|
21 |
+
|
22 |
+
# --- First-order system example: Lotka-Volterra ---
|
23 |
+
a, b, c, d = 1.5, 0.8, 0.5, 0.9
|
24 |
+
def lotka_volterra(t, y):
|
25 |
+
prey, predator = y[0], y[1]
|
26 |
+
d_prey_dt = a * prey - b * prey * predator
|
27 |
+
d_predator_dt = c * prey * predator - d * predator
|
28 |
+
return [d_prey_dt, d_predator_dt]
|
29 |
+
|
30 |
+
print("\nSolving Lotka-Volterra system from t=0 to t=20 with y0=[10, 5]")
|
31 |
+
solution_lv = solve_first_order_ode(lotka_volterra, (0, 20), [10, 5], t_eval_count=200)
|
32 |
+
if solution_lv['success']:
|
33 |
+
print(f"Lotka-Volterra solved. Plot saved to {solution_lv['plot_path']}")
|
34 |
+
else:
|
35 |
+
print(f"Lotka-Volterra failed. Message: {solution_lv['message']}")
|
36 |
+
|
37 |
+
# --- Second-order ODE example: d²y/dt² = -sin(y) (simple pendulum) ---
|
38 |
+
g_L = 9.81 / 1.0 # Example: g/L = 9.81
|
39 |
+
def pendulum_ode(t, y_angle, dy_dt_angular_velocity):
|
40 |
+
return -g_L * np.sin(y_angle)
|
41 |
+
|
42 |
+
print("\nSolving d²y/dt² = -g/L*sin(y), y(0)=pi/4, dy/dt(0)=0 from t=0 to t=10")
|
43 |
+
solution2 = solve_second_order_ode(pendulum_ode, (0, 10), y0=np.pi/4, dy0_dt=0, t_eval_count=100)
|
44 |
+
if solution2['success']:
|
45 |
+
print(f"Second-order ODE solved. Message: {solution2['message']}")
|
46 |
+
if solution2['plot_path']:
|
47 |
+
print(f"Plot saved to {solution2['plot_path']}")
|
48 |
+
else:
|
49 |
+
print(f"Second-order ODE failed. Message: {solution2['message']}")
|
maths/differential_equations/solve_first_order_ode.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Solves a first-order ordinary differential equation (or system of first-order ODEs)
|
3 |
+
dy/dt = f(t, y) with initial condition y(t0) = y0.
|
4 |
+
"""
|
5 |
+
import numpy as np
|
6 |
+
from scipy.integrate import solve_ivp
|
7 |
+
from typing import Callable, List, Tuple, Dict, Any, Union
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
|
10 |
+
ODEFunc = Callable[[float, Union[np.ndarray, List[float]]], Union[np.ndarray, List[float]]]
|
11 |
+
|
12 |
+
def solve_first_order_ode(
|
13 |
+
ode_func: ODEFunc,
|
14 |
+
t_span: Tuple[float, float],
|
15 |
+
y0: List[float],
|
16 |
+
t_eval_count: int = 100,
|
17 |
+
method: str = 'RK45',
|
18 |
+
**kwargs: Any
|
19 |
+
) -> Dict[str, Union[np.ndarray, str, bool]]:
|
20 |
+
# ...existing code...
|
21 |
+
try:
|
22 |
+
y0_np = np.array(y0, dtype=float)
|
23 |
+
t_eval = np.linspace(t_span[0], t_span[1], t_eval_count)
|
24 |
+
|
25 |
+
sol = solve_ivp(ode_func, t_span, y0_np, method=method, t_eval=t_eval, **kwargs)
|
26 |
+
|
27 |
+
plot_path = None
|
28 |
+
if sol.success:
|
29 |
+
try:
|
30 |
+
plt.figure(figsize=(10, 6))
|
31 |
+
if y0_np.ndim == 0 or len(y0_np) == 1 : # Single equation
|
32 |
+
plt.plot(sol.t, sol.y[0], label=f'y(t), y0={y0_np[0] if y0_np.ndim > 0 else y0_np}')
|
33 |
+
else: # System of equations
|
34 |
+
for i in range(sol.y.shape[0]):
|
35 |
+
plt.plot(sol.t, sol.y[i], label=f'y_{i+1}(t), y0_{i+1}={y0_np[i]}')
|
36 |
+
plt.xlabel("Time (t)")
|
37 |
+
plt.ylabel("Solution y(t)")
|
38 |
+
plt.title(f"Solution of First-Order ODE ({method})")
|
39 |
+
plt.legend()
|
40 |
+
plt.grid(True)
|
41 |
+
plot_path = "ode_solution_plot.png"
|
42 |
+
plt.savefig(plot_path)
|
43 |
+
plt.close() # Close the plot to free memory
|
44 |
+
except Exception as e_plot:
|
45 |
+
print(f"Warning: Could not generate plot: {e_plot}")
|
46 |
+
plot_path = None
|
47 |
+
|
48 |
+
return {
|
49 |
+
't': sol.t,
|
50 |
+
'y': sol.y,
|
51 |
+
'message': sol.message,
|
52 |
+
'success': sol.success,
|
53 |
+
'plot_path': plot_path
|
54 |
+
}
|
55 |
+
except Exception as e:
|
56 |
+
return {
|
57 |
+
't': np.array([]),
|
58 |
+
'y': np.array([]),
|
59 |
+
'message': f"Error during ODE solving: {str(e)}",
|
60 |
+
'success': False,
|
61 |
+
'plot_path': None
|
62 |
+
}
|
maths/differential_equations/solve_second_order_ode.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Solves a single second-order ordinary differential equation of the form
|
3 |
+
d²y/dt² = f(t, y, dy/dt) with initial conditions y(t0)=y0 and dy/dt(t0)=dy0_dt.
|
4 |
+
"""
|
5 |
+
import numpy as np
|
6 |
+
from scipy.integrate import solve_ivp
|
7 |
+
from typing import Callable, List, Tuple, Dict, Any, Union
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
|
10 |
+
def solve_second_order_ode(
|
11 |
+
ode_func_second_order: Callable[[float, float, float], float],
|
12 |
+
t_span: Tuple[float, float],
|
13 |
+
y0: float,
|
14 |
+
dy0_dt: float,
|
15 |
+
t_eval_count: int = 100,
|
16 |
+
method: str = 'RK45',
|
17 |
+
**kwargs: Any
|
18 |
+
) -> Dict[str, Union[np.ndarray, str, bool]]:
|
19 |
+
# ...existing code...
|
20 |
+
def system_func(t: float, z: np.ndarray) -> List[float]:
|
21 |
+
y_val, dy_dt_val = z[0], z[1]
|
22 |
+
d2y_dt2_val = ode_func_second_order(t, y_val, dy_dt_val)
|
23 |
+
return [dy_dt_val, d2y_dt2_val]
|
24 |
+
|
25 |
+
initial_conditions_system = [y0, dy0_dt]
|
26 |
+
|
27 |
+
try:
|
28 |
+
t_eval = np.linspace(t_span[0], t_span[1], t_eval_count)
|
29 |
+
sol = solve_ivp(system_func, t_span, initial_conditions_system, method=method, t_eval=t_eval, **kwargs)
|
30 |
+
|
31 |
+
plot_path = None
|
32 |
+
if sol.success:
|
33 |
+
try:
|
34 |
+
plt.figure(figsize=(12, 7))
|
35 |
+
|
36 |
+
plt.subplot(2,1,1)
|
37 |
+
plt.plot(sol.t, sol.y[0], label=f'y(t), y0={y0}')
|
38 |
+
plt.xlabel("Time (t)")
|
39 |
+
plt.ylabel("y(t)")
|
40 |
+
plt.title(f"Solution of Second-Order ODE: y(t) ({method})")
|
41 |
+
plt.legend()
|
42 |
+
plt.grid(True)
|
43 |
+
|
44 |
+
plt.subplot(2,1,2)
|
45 |
+
plt.plot(sol.t, sol.y[1], label=f'dy/dt(t), dy0/dt={dy0_dt}', color='orange')
|
46 |
+
plt.xlabel("Time (t)")
|
47 |
+
plt.ylabel("dy/dt(t)")
|
48 |
+
plt.title(f"Solution of Second-Order ODE: dy/dt(t) ({method})")
|
49 |
+
plt.legend()
|
50 |
+
plt.grid(True)
|
51 |
+
|
52 |
+
plt.tight_layout()
|
53 |
+
plot_path = "ode_second_order_solution_plot.png"
|
54 |
+
plt.savefig(plot_path)
|
55 |
+
plt.close()
|
56 |
+
except Exception as e_plot:
|
57 |
+
print(f"Warning: Could not generate plot: {e_plot}")
|
58 |
+
plot_path = None
|
59 |
+
|
60 |
+
return {
|
61 |
+
't': sol.t,
|
62 |
+
'y': sol.y[0], # First component of the system's solution
|
63 |
+
'dy_dt': sol.y[1], # Second component of the system's solution
|
64 |
+
'message': sol.message,
|
65 |
+
'success': sol.success,
|
66 |
+
'plot_path': plot_path
|
67 |
+
}
|
68 |
+
except Exception as e:
|
69 |
+
return {
|
70 |
+
't': np.array([]),
|
71 |
+
'y': np.array([]),
|
72 |
+
'dy_dt': np.array([]),
|
73 |
+
'message': f"Error during ODE solving: {str(e)}",
|
74 |
+
'success': False,
|
75 |
+
'plot_path': None
|
76 |
+
}
|