spagestic commited on
Commit
279610a
·
1 Parent(s): 5543e04

modularized

Browse files
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.differential_equations import solve_first_order_ode, solve_second_order_ode
 
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: -2*y" or "lambda t, y, dy_dt: -0.1*dy_dt -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
+ }