Continuous¶
Fixed-step ODE integration plus a small library of canonical example systems used throughout the demos and tests.
All continuous models use SI units: - Displacement: metres (m) - Velocity: m/s - Angles: radians (rad)
Quick example¶
import numpy as np
import simweave as sw
msd = sw.MassSpringDamper(mass=1.0, damping=0.4, stiffness=4.0)
res = sw.simulate(msd, t_span=(0.0, 12.0), dt=0.01, x0=np.array([1.0, 0.0]))
Available systems¶
MassSpringDamperSimplePendulumQuarterCarModelHalfCarModelRollCarModelFullCarModelSeriesRLCThermalRCTwoMassThermal
Each implements the DynamicSystem / SupportsDynamics protocol so
custom systems plug into simulate() the same way.
Hybrid: continuous + discrete¶
Wrap a system in ContinuousProcess to register it on a
SimEnvironment. The integrator advances one tick per env.tick()
call, so continuous physics share the clock with queues and agents.
PID Control¶
Please see demo 20_PID_thermal_system.py to see a PID controller applied to a simple ThermalRC system. A temperature regulation system responding to workload changes could be helpful in real-world cases like: - CPUs - batteries - industrial heaters - HVAC systems
Vehicle dynamics examples¶
Quarter car¶
A 2 DOF (Degrees of Freedom) model with 4 variables (position + velocity).
- z_s → sprung mass (body)
- z_u → unsprung mass (wheel)
d = sw.QuarterCarModel(250, 40, 15000, 1500, 200000)
r = sw.simulate(d, (0.0, 2.0), dt=0.001, inputs=lambda t: 0.01)
demos/10_quarter_car.py
Half car (pitch)¶
A 4 DOF (Degrees of Freedom) model for simulating pitch movement between front and back.
- z_s → vertical body motion
- theta → pitch
- z_uf → front wheel
- z_ur → rear wheel
model = sw.HalfCarModel(
1200, 2500, 60, 60,
20000, 20000,
1500, 1500,
150000, 150000,
1.2, 1.6
)
r = sw.simulate(model, (0.0, 2.0), dt=0.001,
inputs=lambda t: (0.01, 0.01))
demos/17_half_car_pitch.py
Roll model (left/right)¶
A 4 DOF (Degrees of Freedom) model for simulating roll movement between left and right.
- z_s → vertical body motion
- phi → roll
- z_ul → left wheel
- z_ur → right wheel
model = sw.RollCarModel(
1200, 2200, 60, 60,
20000, 20000,
1500, 1500,
150000, 150000,
1.6
)
r = sw.simulate(model, (0.0, 2.0), dt=0.001,
inputs=lambda t: (0.01, 0.0))
demos/18_half_car_roll_model.py
Full Car Model¶
A 7 DOF (Degrees of Freedom) model is included, extending prior examples.
- z_s → vertical body motion
- theta → pitch
- phi → roll
- z_ufl → front-left
- z_ufr → front-right
- z_url → rear-left
- z_urr → rear-right
model = sw.FullCarModel(
sprung_mass=1200,
pitch_inertia=1500,
roll_inertia=800,
unsprung_mass=40,
k_s=20000,
c_s=1500,
k_t=200000,
a=1.2,
b=1.3,
track_width=1.6,
)
result = simulate(
model, (0.0, 3.0),
dt=0.001,
inputs=lambda t: np.array([0.01, 0.01, 0.0, 0.0]) if t > 1 else np.zeros(4),
)
fig = sw.plot_vehicle_metrics(
res,
title="Full car response (bump input)",
model=model,
)
demos/19_full_car_dynamics.py
Suspension Controllers¶
From v0.6.0, SimWeave includes controllers to help optimise dynamics according to prefered performance criteria: - Comfort (Acceleration of Vertical Vibrations) - Grip (Dynamic Tyre Loading) - Hybrid
These are realised by Skyhook and Groundhook control. See: - simweave.continuous.control.suspension.SkyhookDamper - simweave.continuous.control.suspension.GroundhookDamper - simweave.continuous.control.suspension.HybridActiveDamper
The former imagines one end of a damper 'connected to the sky' (an inertial reference point). The latter imagines one end affixed to the ground. This ensure dissipative force applies with respect to body movement, or wheel movement only as opposed to the releative velocities.
Example controller usage:
passive = QuarterCarModel(250, 40, 15000, 1500, 200000)
controlled = QuarterCarModel(250, 40, 15000, 1500, 200000, controller=SkyhookDamper(1500))
r_passive = simulate(passive, (0, 2), dt=0.001, inputs=lambda t: 0.01)
r_control = simulate(controlled, (0, 2), dt=0.001, inputs=lambda t: 0.01)
z_passive = r_passive.state[:, 0]
z_control = r_control.state[:, 0]
print(f"Standard deviation controlled system: {z_control.std()}")
print(f"Standard deviation passive system: {z_passive.std()}")
A difference between an idealised behaviour, and the passive suspension can be used as a control signal. This could be used by an actuator in place of a damper, capable of adding energy into the system as a 'Fully Active' system. Alternatively, it could be a variable damping coefficient e.g. magnetorheological (MR) dampers.
To ensure fidelity with a real-world suspension that is only dissipative in nature, a wrapper allows for controller force to only apply when Force * relative velocity is negative.
See: simweave.continuous.control.suspension.SemiActiveWrapper
Example wrapper usage:API¶
Continuous-time dynamics: state-space solver, plug-in systems, hybrid wrapper.
DynamicSystem
¶
Base class for first-order state-space dynamic systems.
SupportsDynamics
¶
Bases: Protocol
Protocol for plug-in dynamic systems.
ContinuousProcess
¶
ContinuousProcess(system: SupportsDynamics, method: str = 'rk4', n_substeps: int = 1, inputs: InputFunc | None = None, name: str | None = None)
Bases: Entity
Drive a DynamicSystem from a SimEnvironment's tick loop.
Each tick, integrates n_substeps sub-intervals of length dt / n_substeps.
The previous state is appended to history_state with the corresponding
time in history_time.
Source code in src/simweave/continuous/solver.py
PIDController
¶
Generic PID controller.
u(t) = Kp * e + Ki * ∫e dt + Kd * de/dt
Source code in src/simweave/continuous/control/pid.py
SkyhookDamper
¶
GroundhookDamper
¶
HybridActiveDamper
¶
SemiActiveWrapper
¶
MassSpringDamper
¶
MassSpringDamper(mass: float | Mass, damping: float | Damping, stiffness: float | SpringStiffness, x0: tuple[float, float] = (0.0, 0.0))
Bases: DynamicSystem
Single degree-of-freedom mass-spring-damper.
State x = [displacement, velocity] Equation: m x'' + c x' + k x = F(t)
Source code in src/simweave/continuous/systems/mass_spring_damper.py
SimplePendulum
¶
SimplePendulum(length: float | Distance, mass: float | Mass = 1.0, gravity: float | Acceleration = 9.81, damping: float | Damping = 0.0, x0: tuple[float, float] = (0.0, 0.0))
Bases: DynamicSystem
Simple pendulum with optional viscous damping and external torque.
State x = [theta, theta_dot] Equation: theta'' + (c / (m l^2)) theta' + (g/l) sin(theta) = tau / (m l^2)
Source code in src/simweave/continuous/systems/pendulum.py
QuarterCarModel
¶
QuarterCarModel(sprung_mass: float | Mass, unsprung_mass: float | Mass, suspension_stiffness: float | SpringStiffness, damping: float | Damping, tyre_stiffness: float | SpringStiffness, x0: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0), controller=None)
Bases: DynamicSystem
Two degree-of-freedom quarter-car suspension model.
State vector x = [z_s, z_s_dot, z_u, z_u_dot] where: z_s: sprung mass displacement z_u: unsprung mass displacement
Source code in src/simweave/continuous/systems/quarter_car.py
HalfCarModel
¶
HalfCarModel(sprung_mass: float | Mass, pitch_inertia: float | Inertia, unsprung_mass_front: float | Mass, unsprung_mass_rear: float | Mass, k_sf: float | SpringStiffness, k_sr: float | SpringStiffness, c_sf: float | Damping, c_sr: float | Damping, k_tf: float | SpringStiffness, k_tr: float | SpringStiffness, a: float | Distance, b: float | Distance, x0: tuple[float, ...] = (0.0,) * 8, controller=None)
Bases: DynamicSystem
Half-car suspension model with pitch dynamics.
State vector: x = [z_s, z_s_dot, theta, theta_dot, z_uf, z_uf_dot, z_ur, z_ur_dot]
Source code in src/simweave/continuous/systems/half_car.py
RollCarModel
¶
RollCarModel(sprung_mass: float | Mass, roll_inertia: float | Inertia, unsprung_mass_left: float | Mass, unsprung_mass_right: float | Mass, k_sl: float | SpringStiffness, k_sr: float | SpringStiffness, c_sl: float | Damping, c_sr: float | Damping, k_tl: float | SpringStiffness, k_tr: float | SpringStiffness, track_width: float | Distance, x0: tuple[float, ...] = (0.0,) * 8, controller=None)
Bases: DynamicSystem
Roll-only vehicle model (left-right dynamics).
Source code in src/simweave/continuous/systems/roll_car.py
FullCarModel
¶
FullCarModel(sprung_mass: float | Mass, pitch_inertia: float | Inertia, roll_inertia: float | Inertia, unsprung_mass: float | Mass, k_s: float | SpringStiffness, c_s: float | Damping, k_t: float | SpringStiffness, a: float | Distance, b: float | Distance, track_width: float | Distance, x0: tuple[float, ...] = (0.0,) * 14, controller=None)
Bases: DynamicSystem
Full car model with pitch and roll dynamics.
Source code in src/simweave/continuous/systems/full_car.py
SeriesRLC
¶
SeriesRLC(resistance: float | Resistance, inductance: float | Inductance, capacitance: float | Capacitance, x0: tuple[float, float] = (0.0, 0.0))
Bases: DynamicSystem
Series RLC circuit in charge-current form.
State x = [q, i] Equation: L q'' + R q' + (1/C) q = V(t)
Source code in src/simweave/continuous/systems/rlc.py
ThermalRC
¶
ThermalRC(thermal_resistance: float | ThermalResistance, thermal_capacitance: float | ThermalCapacitance, ambient_temperature: float | Temperature = 293.15, initial_temperature: float | Temperature = 293.15, controller: None | PIDController = None)
Bases: DynamicSystem
First-order lumped-capacitance thermal model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thermal_resistance
|
float | ThermalResistance
|
R_th in K/W. Larger => slower equilibration with ambient. |
required |
thermal_capacitance
|
float | ThermalCapacitance
|
C_th in J/K. Larger => more thermal inertia. |
required |
ambient_temperature
|
float | Temperature
|
T_inf in Kelvin (or any consistent unit). Default 293.15 K (20 C). |
293.15
|
initial_temperature
|
float | Temperature
|
Starting body temperature, same unit as |
293.15
|
Source code in src/simweave/continuous/systems/thermal.py
time_constant
property
¶
First-order time constant tau = R_th * C_th (seconds).
TwoMassThermal
¶
TwoMassThermal(C_core: float | ThermalCapacitance, C_sink: float | ThermalCapacitance, k_core_to_sink: float | ThermalConductance, R_sink_to_ambient: float | ThermalResistance, ambient_temperature: float | Temperature = 293.15, initial_core: float | Temperature = 293.15, initial_sink: float | Temperature = 293.15)
Bases: DynamicSystem
Two-lump thermal model: core <-> sink <-> ambient.
State: [T_core, T_sink], both in the same unit as ambient.
Q_in(t) is applied to the core. Sink dissipates to ambient via
R_sink_to_ambient; core couples to sink via conductance
k_core_to_sink (W/K).
Source code in src/simweave/continuous/systems/thermal.py
simulate
¶
simulate(system: SupportsDynamics, t_span: tuple[float, float], dt: float, x0: Iterable[float] | ndarray | None = None, method: str = 'rk4', inputs: Optional[InputFunc] = None) -> SimulationResult
Integrate a system over t_span with fixed step dt.