A modular dynamics library for control systems research
Project description
๐ SimDyn
A Modular Dynamics Library for Control Systems Research
SimDyn is a unified Python library providing standardized implementations of dynamical systems commonly used in robotics and aerospace control research. The library emphasizes:
- Consistency: All systems share a common interface
- Completeness: Each system provides dynamics, Jacobians, and constraints
- Flexibility: Support for disturbances, parameter variations, and different discretization schemes
- Research-ready: Designed for control synthesis, trajectory optimization, and data-driven methods
Installation
# Install from PyPI
pip install simdyn
# Or install with visualization support
pip install simdyn[viz]
From Source
# Clone the repository
git clone https://github.com/shiivashaakeri/simdyn.git
cd simdyn
# Install in development mode
pip install -e .
# Or install with optional dependencies
pip install -e ".[dev]"
Requirements
- Python 3.10+
- NumPy
- SciPy
Optional Dependencies
- matplotlib (visualization)
Quick Start
import numpy as np
import simdyn as ds
# Create a simple pendulum
pendulum = ds.create_normalized_pendulum()
# Initial state: displaced from bottom
x0 = np.array([0.5, 0.0]) # [angle, angular_velocity]
# Define a controller (or use zero for free dynamics)
controller = lambda t, x: np.array([0.0])
# Simulate for 10 seconds
t, x, u = pendulum.simulate(x0, controller, t_span=(0, 10), dt=0.01)
# Check energy conservation
E_initial = pendulum.energy(x[0])['total']
E_final = pendulum.energy(x[-1])['total']
print(f"Energy drift: {abs(E_final - E_initial):.6f}")
Available Systems
| System | State Dim | Control Dim | Type | Description |
|---|---|---|---|---|
| DoubleIntegrator | 2-6 | 1-3 | Linear | Point mass in 1D/2D/3D |
| Unicycle | 3 | 2 | Nonholonomic | 2D mobile robot |
| Pendulum | 2 | 1 | Nonlinear | Classic swing-up benchmark |
| CartPole | 4 | 1 | Underactuated | Inverted pendulum on cart |
| Rocket3DoF | 7 | 3 | Nonlinear | Point-mass powered descent |
| Rocket6DoF | 14 | 3 | Nonlinear | Rigid-body rocket with attitude |
Core Features
Unified Interface
All systems inherit from DynamicalSystem and provide:
# Continuous-time dynamics
x_dot = system.f(x, u, w) # w is optional disturbance
# Discrete-time dynamics
x_next = system.f_discrete(x, u, dt, w, method='rk4')
# Analytical Jacobians
A = system.A(x, u) # โf/โx
B = system.B(x, u) # โf/โu
# Linearization at operating point
A, B, c = system.linearize(x0, u0)
# Constraint checking
lb, ub = system.get_state_bounds()
lb, ub = system.get_control_bounds()
# Closed-loop simulation
t, x, u = system.simulate(x0, controller, t_span, dt)
Disturbance Handling
All dynamics accept an optional disturbance input:
# Process noise
w = np.random.randn(system.n_disturbance) * 0.01
x_dot = system.f(x, u, w)
# Simulate with disturbance function
def disturbance_fn(t, x):
return np.random.randn(system.n_disturbance) * 0.01
t, x, u = system.simulate(x0, controller, t_span, dt, disturbance_fn=disturbance_fn)
Jacobian Verification
Verify analytical Jacobians against numerical differentiation:
passed, errors = system.verify_jacobians(x, u, eps=1e-7, tol=1e-5)
if not passed:
print(f"Jacobian errors: {errors}")
Parameterized Systems (Digital Twins)
Create system variants with different parameters:
# True system
params_true = ds.Rocket3DoFParams(m_wet=2.0, I_sp=30.0)
rocket_true = ds.Rocket3DoF(params_true)
# Model with parameter mismatch
params_model = ds.Rocket3DoFParams(m_wet=1.9, I_sp=28.0) # 5% error
rocket_model = ds.Rocket3DoF(params_model)
# Collect data from true system, design control with model
Examples
Rocket Landing
import numpy as np
import simdyn as ds
# Create 3-DoF rocket with normalized parameters
rocket = ds.create_normalized_rocket3dof()
# Initial state: mass=2, altitude=10, falling
x0 = rocket.pack_state(
mass=2.0,
position=np.array([10.0, 2.0, 0.0]),
velocity=np.array([-2.0, 1.0, 0.0])
)
# Simple gravity-turn controller
def landing_controller(t, x):
pos = rocket.get_position(x)
vel = rocket.get_velocity(x)
mass = rocket.get_mass(x)
g = rocket.params.g_I
# Thrust to cancel gravity + velocity damping
T = -mass * g - 2.0 * vel
T_mag = np.clip(np.linalg.norm(T), rocket.params.T_min, rocket.params.T_max)
if np.linalg.norm(T) > 1e-6:
return T / np.linalg.norm(T) * T_mag
return rocket.hover_thrust(x)
# Simulate
t, x, u = rocket.simulate(x0, landing_controller, (0, 8), dt=0.01, method='rk4')
print(f"Final altitude: {rocket.get_altitude(x[-1]):.3f}")
print(f"Final velocity: {rocket.get_speed(x[-1]):.3f}")
print(f"Fuel used: {1 - rocket.fuel_fraction(x[-1]):.1%}")
Cart-Pole Swing-Up
import numpy as np
import simdyn as ds
cartpole = ds.create_cartpole()
# Start with pole down
x0 = np.array([0.0, 0.0, np.pi, 0.0])
# Energy-based swing-up + balancing controller
E_target = cartpole.energy_upright()
def swing_up_controller(t, x):
cart_x, cart_v, theta, omega = x
E = cartpole.energy(x)['total']
# Near upright: switch to balancing
if abs(theta) < 0.3:
F = 50*theta + 15*omega + cart_x + 2*cart_v
else:
# Energy pumping
F = 5.0 * np.sign(omega * np.cos(theta)) * (E_target - E)
return np.array([np.clip(F, -10, 10)])
t, x, u = cartpole.simulate(x0, swing_up_controller, (0, 10), dt=0.02)
LQR Control Design
import numpy as np
from scipy import linalg
import simdyn as ds
# Create pendulum
pendulum = ds.create_normalized_pendulum()
# Linearize at upright equilibrium
A, B = pendulum.linearize_at_top()
# LQR weights
Q = np.diag([10.0, 1.0]) # state cost
R = np.array([[0.1]]) # control cost
# Solve Riccati equation
P = linalg.solve_continuous_are(A, B, Q, R)
K = np.linalg.inv(R) @ B.T @ P
# LQR controller (relative to upright)
x_target = np.array([np.pi, 0.0])
def lqr_controller(t, x):
x_error = x - x_target
x_error[0] = np.arctan2(np.sin(x_error[0]), np.cos(x_error[0])) # wrap angle
return -K @ x_error
# Simulate from near upright
x0 = np.array([np.pi - 0.1, 0.0])
t, x, u = pendulum.simulate(x0, lqr_controller, (0, 5), dt=0.01)
Conventions
Coordinate Frames
| System | Inertial Frame | Body Frame |
|---|---|---|
| Rocket 3-DoF/6-DoF | UEN (x=up) | x=thrust axis |
| Quadrotor | NED or ENU | x=forward |
| Unicycle/Bicycle | 2D XY plane | x=forward |
Quaternions
- Convention: Scalar-first
q = [q_w, q_x, q_y, q_z] - Rotation:
q_BItransforms inertial vectors to body frame - Identity:
[1, 0, 0, 0]
Units
- Default: SI units (meters, seconds, kilograms, radians)
- Normalized parameters available for algorithm testing
See docs/conventions.md for full details.
Testing
# Run all tests
pytest
# Run with coverage
pytest --cov=simdyn
# Run specific test file
pytest tests/systems/test_rocket6dof.py -v
Project Structure
simdyn/
โโโ src/simdyn/
โ โโโ __init__.py # Package exports
โ โโโ base.py # Abstract base class
โ โโโ systems/ # System implementations
โ โ โโโ double_integrator.py
โ โ โโโ unicycle.py
โ โ โโโ pendulum.py
โ โ โโโ cartpole.py
โ โ โโโ rocket3dof.py
โ โ โโโ rocket6dof.py
โ โโโ integrators/ # Numerical integrators
โ โ โโโ euler.py
โ โ โโโ rk4.py
โ โโโ utils/ # Utilities
โ โโโ rotations.py # SO(3), Euler angles
โ โโโ quaternion.py # Quaternion operations
โโโ tests/ # Comprehensive test suite
โโโ examples/ # Example scripts
โโโ docs/ # Documentation
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE for details.
References
- Szmuk, M., & Aรงฤฑkmeลe, B. (2018). Successive convexification for 6-DoF powered descent guidance with compound state-triggered constraints.
- Tedrake, R. Underactuated Robotics (MIT OCW).
- Florian, R. V. (2007). Correct equations for the dynamics of the cart-pole system.
Acknowledgments
Developed for control systems research at University of Washington.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simdyn-0.1.0.tar.gz.
File metadata
- Download URL: simdyn-0.1.0.tar.gz
- Upload date:
- Size: 76.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c91d5c918616f37141631ba7616efd0fa44cac1e48bc3f244081715de4ff2a98
|
|
| MD5 |
29d42118799b5384c5f0e977b0e2a661
|
|
| BLAKE2b-256 |
f57d5ea498bdff8ea33911bb4cb7e569e7e315495fe8e18634a5362b70427469
|
Provenance
The following attestation bundles were made for simdyn-0.1.0.tar.gz:
Publisher:
release.yml on shiivashaakeri/simdyn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simdyn-0.1.0.tar.gz -
Subject digest:
c91d5c918616f37141631ba7616efd0fa44cac1e48bc3f244081715de4ff2a98 - Sigstore transparency entry: 771723942
- Sigstore integration time:
-
Permalink:
shiivashaakeri/simdyn@bbab987fbb3ce38311c7b07f4d788ec58b24f15d -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shiivashaakeri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bbab987fbb3ce38311c7b07f4d788ec58b24f15d -
Trigger Event:
push
-
Statement type:
File details
Details for the file simdyn-0.1.0-py3-none-any.whl.
File metadata
- Download URL: simdyn-0.1.0-py3-none-any.whl
- Upload date:
- Size: 71.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69f9cf4643e00a1439b0fc40c6b77f35a6667ac3d22e447174aa56d7c0640900
|
|
| MD5 |
a7798a29afb208efe3bbb5bfbbcb0383
|
|
| BLAKE2b-256 |
ceaa6e84fb5bf3e1b3b8f62a43c00d70b80a863ac740d33fb166d939e53ab640
|
Provenance
The following attestation bundles were made for simdyn-0.1.0-py3-none-any.whl:
Publisher:
release.yml on shiivashaakeri/simdyn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simdyn-0.1.0-py3-none-any.whl -
Subject digest:
69f9cf4643e00a1439b0fc40c6b77f35a6667ac3d22e447174aa56d7c0640900 - Sigstore transparency entry: 771723955
- Sigstore integration time:
-
Permalink:
shiivashaakeri/simdyn@bbab987fbb3ce38311c7b07f4d788ec58b24f15d -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shiivashaakeri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bbab987fbb3ce38311c7b07f4d788ec58b24f15d -
Trigger Event:
push
-
Statement type: