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 |
+
}
|