Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

jobs:
lint-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: pip install -r requirements.txt

- name: Lint with black
run: black --check .

- name: Lint with ruff
run: ruff check .

- name: Type-check with mypy
run: mypy . --ignore-missing-imports

- name: Run tests with pytest
run: pytest tests/ -v
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .modpods import *
from .modpods import * # noqa: F403
4,084 changes: 3,012 additions & 1,072 deletions modpods.py

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[tool.black]
line-length = 88
target-version = ["py310", "py311", "py312"]

[tool.ruff]
line-length = 88
target-version = "py310"

[tool.ruff.lint]
select = ["E", "F", "W", "I"]
ignore = ["E501"]

[tool.mypy]
ignore_missing_imports = true
no_strict_optional = true
warn_return_any = true
check_untyped_defs = true

[tool.pytest.ini_options]
testpaths = ["tests"]
markers = [
"slow: marks tests as slow (uses large data files or long simulations)",
]
21 changes: 21 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Core dependencies for modpods
numpy>=1.24
pandas>=2.0
scipy>=1.10
matplotlib>=3.7
control>=0.9
pysindy>=2.0
cvxpy>=1.3
networkx>=3.0
pyswmm>=2.0
pystorms>=1.0
statsmodels>=0.14
dill>=0.3

# Testing
pytest>=7.0

# Linting
black>=23.0
ruff>=0.1
mypy>=1.0
99 changes: 63 additions & 36 deletions spring-cart-interactive.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,120 @@
# not at all working

import numpy as np
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
from matplotlib import animation
from scipy.integrate import solve_ivp
import ipywidgets as widgets
from ipywidgets import interact

# Parameters
m_cart = 1.0 # Mass of the cart (kg)
m_pend = 0.1 # Mass of the pendulum (kg)
l_pend = 1.0 # Length of the pendulum (m)
g = 9.81 # Gravitational acceleration (m/s^2)
m_cart = 1.0 # Mass of the cart (kg)
m_pend = 0.1 # Mass of the pendulum (kg)
l_pend = 1.0 # Length of the pendulum (m)
g = 9.81 # Gravitational acceleration (m/s^2)
damping = 0.1 # Damping factor

# Control Parameters (PD Controller)
Kp = 100.0 # Proportional gain
Kd = 20.0 # Derivative gain
Kd = 20.0 # Derivative gain


# Linearized system dynamics
def linear_system(t, state, stiffness, control_input, disturbance):
x, x_dot, theta, theta_dot = state
sin_theta = np.sin(theta)
cos_theta = np.cos(theta)

# Control input through spring
u = -Kp * theta - Kd * theta_dot + control_input
u_spring = stiffness * u

# Equations of motion (linearized)
x_ddot = (u_spring + disturbance - m_pend * l_pend * theta_dot**2 * sin_theta + m_pend * g * sin_theta * cos_theta) / \
(m_cart + m_pend * sin_theta**2)
x_ddot = (
u_spring
+ disturbance
- m_pend * l_pend * theta_dot**2 * sin_theta
+ m_pend * g * sin_theta * cos_theta
) / (m_cart + m_pend * sin_theta**2)
theta_ddot = (g * sin_theta - cos_theta * x_ddot) / l_pend

return [x_dot, x_ddot, theta_dot, theta_ddot]


# Solve the system with initial conditions
def solve_pendulum(stiffness, control_input, disturbance, t_max=10):
t_span = (0, t_max)
y0 = [0, 0, np.pi / 12, 0] # Initial conditions: [x, x_dot, theta, theta_dot]
t_eval = np.linspace(0, t_max, 300)

# Integrate the system
sol = solve_ivp(linear_system, t_span, y0, args=(stiffness, control_input, disturbance), t_eval=t_eval, method='RK45')
sol = solve_ivp(
linear_system,
t_span,
y0,
args=(stiffness, control_input, disturbance),
t_eval=t_eval,
method="RK45",
)
return sol.t, sol.y


# Animate the system
def animate_pendulum(stiffness, control_input, disturbance):
t, y = solve_pendulum(stiffness, control_input, disturbance)
x = y[0, :] # Cart position
x = y[0, :] # Cart position
theta = y[2, :] # Pendulum angle

fig, ax = plt.subplots(figsize=(8, 4))
ax.set_xlim(-5, 5)
ax.set_ylim(-2, 2)

# Elements of the system: cart and pendulum
cart, = ax.plot([], [], 'o-', lw=2)
pendulum, = ax.plot([], [], 'o-', lw=2)
(cart,) = ax.plot([], [], "o-", lw=2)
(pendulum,) = ax.plot([], [], "o-", lw=2)

def init():
cart.set_data([], [])
pendulum.set_data([], [])
return cart, pendulum

def update(frame):
cart.set_data([x[frame] - 0.5, x[frame] + 0.5], [0, 0]) # Draw cart
pendulum.set_data([x[frame], x[frame] + l_pend * np.sin(theta[frame])],
[0, -l_pend * np.cos(theta[frame])]) # Draw pendulum
pendulum.set_data(
[x[frame], x[frame] + l_pend * np.sin(theta[frame])],
[0, -l_pend * np.cos(theta[frame])],
) # Draw pendulum
return cart, pendulum

# Slow down animation
ani = animation.FuncAnimation(fig, update, frames=len(t), init_func=init, blit=True, interval=100) # Interval adjusted to 100ms

plt.title('Inverted Pendulum on Cart')
animation.FuncAnimation(
fig, update, frames=len(t), init_func=init, blit=True, interval=100
) # Interval adjusted to 100ms

plt.title("Inverted Pendulum on Cart")
plt.grid(True)
plt.show()


# Interactive widgets for spring stiffness, control input, and disturbance
stiffness_slider = widgets.FloatSlider(min=0.1, max=10.0, step=0.1, value=1.0, description="Spring Stiffness")
control_input_slider = widgets.FloatSlider(min=-10.0, max=10.0, step=0.1, value=0.0, description="Control Input")
disturbance_slider = widgets.FloatSlider(min=-5.0, max=5.0, step=0.1, value=0.0, description="Disturbance")
stiffness_slider = widgets.FloatSlider(
min=0.1, max=10.0, step=0.1, value=1.0, description="Spring Stiffness"
)
control_input_slider = widgets.FloatSlider(
min=-10.0, max=10.0, step=0.1, value=0.0, description="Control Input"
)
disturbance_slider = widgets.FloatSlider(
min=-5.0, max=5.0, step=0.1, value=0.0, description="Disturbance"
)

# Ensure ipywidgets work in Jupyter by using `interact`
if 'get_ipython' in globals(): # Checks if running in a Jupyter environment
interact(animate_pendulum,
stiffness=stiffness_slider,
control_input=control_input_slider,
disturbance=disturbance_slider)
if "get_ipython" in globals(): # Checks if running in a Jupyter environment
interact(
animate_pendulum,
stiffness=stiffness_slider,
control_input=control_input_slider,
disturbance=disturbance_slider,
)
else:
# If not running in Jupyter, manually call animation for default parameters
animate_pendulum(1.0, 0.0, 0.0)
104 changes: 0 additions & 104 deletions test.py

This file was deleted.

Loading