A powerful toolkit for analyzing N-dimensional ODE systems with dynamic variable configuration. Change one number and everything adapts automaticallyβno more manual loop adjustments or broken variable unpacking.
N_VARS = 3 # Just change this for number of variablesimport sage.all as sage
import numpy as np
from ic_generator import generate_ic_grid, get_ic_grid_info
from state_helpers import StateAccessor
from ode_viewer_nD import ODESystemND, ODEViewerNDIC_CENTER = 0.0 # Where the center of IC grid is
IC_SPREAD = 2.0 # How far the IC grid spreads too
ICS_PER_VAR = 2 # How many ICs to put on each axisic_grid = generate_ic_grid(N_VARS, IC_CENTER, IC_SPREAD, ICS_PER_VAR)
total_ics = get_ic_grid_info(N_VARS, ICS_PER_VAR)| Module | Purpose | Usage |
|---|---|---|
| ic_generator.py | Generates N-dimensional IC grids automatically | generate_ic_grid(n_vars, ic_center, ic_spread, ics_per_var) |
| state_helpers.py | Makes accessing state variables easier (optional) | StateAccessor(state, var_names) |
| ode_viewer_nD.py | Solve and visualize ODE systems in any dimension | ODEViewerND().plot_all_3d() |
import sage.all as sage
import numpy as np
from ic_generator import generate_ic_grid, get_ic_grid_info
from state_helpers import StateAccessor
from ode_viewer_nD import ODESystemND, ODEViewerND
# === CONFIGURATION ===
N_VARS = 3
IC_CENTER = 0.0
IC_SPREAD = 2.0
ICS_PER_VAR = 2
# For a more general initial condition grid
ic_grid = generate_ic_grid(
n_vars=3,
ic_center=[0.0, 0.5, -0.5], # Per-variable centers
ic_spread=[1.0, 2.0, 0.5], # Per-variable spreads
ics_per_var=[2, 3, 2] # Different counts per dimension
)
# === SYSTEM (circular sin) ===
VAR_NAMES = [f"x_{i}" for i in range(N_VARS)]
def system_func(t, state):
# Works for any N_VARS
derivs = []
for i in range(N_VARS):
next_idx = (i + 1) % N_VARS
derivs.append(np.sin(state[next_idx]))
return derivs
# === SOLVE ===
ic_grid = generate_ic_grid(N_VARS, IC_CENTER, IC_SPREAD, ICS_PER_VAR)
total_ics = get_ic_grid_info(N_VARS, ICS_PER_VAR)
system = ODESystemND(system_func, N_VARS, VAR_NAMES)
t_eval = np.linspace(-5, 50, 1000)
viewer = ODEViewerND()
viewer.solve_ics_grid(system, (-5, 50), ic_grid, total_ics, t_eval=t_eval)
viewer.plot_all_3d(projection_axes=[0, 1, 2])Example: Lorenz Attractor
def system_func(t, state):
s = StateAccessor(state, VAR_NAMES)
p = 10
r = 28
b = 8 / 3
dx = p * (s.x_1 - s.x)
dy = s.x * (r - s.x_2) - s.x_1
dz = s.x * s.x_1 - b * s.x_2
return [dx, dy, dz]Example: Simple two city model
VAR_NAMES = ['P', 'E', 'M']
def system_func(t, state):
s = StateAccessor(state, VAR_NAMES)
carrying_cap = 1.0
growth_rate = 0.3
death_rate = 0.15
capture_rate = 0.4
war_money = 0.2
dP = carrying_cap * tanh(growth_rate * s.E - capture_rate * s.M) - death_rate * s.P
dE = carrying_cap * tanh(capture_rate * s.P) + war_money * s.M - growth_rate * s.E
dM = carrying_cap * tanh(capture_rate * s.P + war_money * (s.M - s.E)) - (carrying_cap / 3) * tanh(s.P) * tanh(s.E)
return [dP, dE, dM]def system_func(t, state):
derivs = []
for i in range(N_VARS):
next_idx = (i + 1) % N_VARS
derivs.append(np.sin(state[next_idx]))
return derivsdef system_func(t, state):
s = StateAccessor(state, VAR_NAMES)
dx = s.x * s.y
dy = s.y * s.z
dz = s.z * s.x
return [dx, dy, dz]N_VARS = 4 # 4th order
ODE_ORDER = 4
VAR_NAMES = ["x"] + [f"x^{(i)}" for i in range(1, ODE_ORDER)]
def highest_derivative(t, state):
x, x1, x2, x3 = state
return x * x2 + t ** 2
ic_grid = generate_ic_grid(N_VARS, 0, 1, 2)
system = ODESystemND.from_higher_order(highest_derivative, ODE_ORDER, VAR_NAMES)- Open cooresponding notebook for that system
- Set up configurations for ICs and display
- Define the system (Can
StateAccessorfor readable state access)
- β Change parameters easily β everything updates
- β Flexible configuration -> scalar or per-variable parameters
- β StateAccessor -> for easy access to state variables
- β Scales to any dimensions -> (2D, 3D, 5D, 10D, ...)
- β Check
VAR_NAMEShasN_VARSelements - β Test your
system_funcwith differentN_VARSvalues - β Ensure system equations don't have hardcoded indices
ic_grid = generate_ic_grid(
n_vars=N_VARS,
ic_center=[0.0, 0.5, -0.5], # List: per-variable
ic_spread=[1.0, 2.0, 0.5], # List: per-variable
ics_per_var=[2, 3, 2] # List: per-variable
)ic_generator.py- Auto initial condition grid generationstate_helpers.py- System function helpers for dynamic variable handling (Useful when you want unpacking-like behavior for arbitrary numbers of variables)ode_viewer_nD.py- ODE solving and visualization library- Jupyter notebooks:
ODE_Viewer.ipynb,ODE_2System_Viewer.ipynb,ODE_NSystem_Viewer.ipynb- For analysis and viewing
The toolkit includes Jupyter notebooks demonstrating various use cases:
- ODE_Viewer.ipynb - Basic ODE visualization
- ODE_2System_Viewer.ipynb - Two-variable systems (with poincare section)
- ODE_NSystem_Viewer.ipynb - General n-dimensional systems
-
Refactor notebooks into one single w/ functions in their own library
-
Add better customizability
- Show/hide init condition points and poincare intersections
- Animation autoscaling
-
Fix first and last frames of animation from being outputted
-
Make plane sides configurable
-
Fix plane so it looks normal past boundary
-
Add option for center to be actual center or only positive (use all axis or just positive nums)
-
Create graph of fixed points (color coded based on stability) on poincare plots
-
Use better fixed point approximations
-
Make possible poincare sections not just planes
- Cylinder
- Sphere (Mercador Projection?)
- Multiple planes
- Torus
-
3D Poincare plot