Plant Models¶
Plants represent the mathematical systems that are being controlled. OpenSMC provides 9 pre-defined plant models that include system dynamics, parameters, and built-in disturbance profiles.
Usage Example¶
from opensmc.plants import Quadrotor6DOF
# Create a 6-DOF Quadrotor plant model
plant = Quadrotor6DOF()
# Get the time-derivative of the state for a given state x and input u
x0 = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
dx_dt = plant.dynamics(t=0.0, x=x0, u=[0.5, 0.5, 0.5, 0.5])
Available Plants¶
plants
¶
OpenSMC Plants — 9 dynamical system implementations.
Classes¶
DoubleIntegrator
¶
Bases: Plant
Double integrator: xddot = (u + d) / J.
States: x = [position, velocity] Input: u (scalar force/torque) Output: y = position
Parameters¶
J : float Inertia (default 1.0). Liu (2017) uses J=2.
Source code in opensmc/plants/double_integrator.py
Functions¶
dynamics(t, x, u, d=None)
¶
Compute xdot = [velocity, (u + d) / J].
Parameters¶
t : float Time. x : ndarray (2,) State [position, velocity]. u : float Control input (force or torque). d : float or None External disturbance (default 0).
Returns¶
xdot : ndarray (2,)
Source code in opensmc/plants/double_integrator.py
get_default_x0()
¶
DoublePendulumCrane
¶
Bases: Plant
Double-pendulum overhead crane (Qian & Yi 2015, Eq. 2.46).
Lagrangian formulation: M(q)qddot + C(q,qdot)qdot + G(q) = [u; 0; 0] where q = [x_trolley, phi, theta].
States: x = [x_trolley, x_dot, phi, phi_dot, theta, theta_dot] Input: u (scalar horizontal force on trolley) [N] Output: y = [x_trolley, phi, theta]
Parameters¶
M : float Trolley mass [kg] (default 50.0). m1 : float Hook mass [kg] (default 10.0). m2 : float Payload (cargo) mass [kg] (default 2.0). l1 : float Upper cable length [m] (default 3.0). l2 : float Lower cable length [m] (default 0.3). b : float Trolley friction [N*s/m] (default 0.0). g : float Gravitational acceleration [m/s^2] (default 9.81).
Source code in opensmc/plants/crane.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | |
Functions¶
coriolis_matrix(phi, theta, dphi, dtheta)
¶
Coriolis/centripetal matrix C(q, qdot) -- 3x3.
Source code in opensmc/plants/crane.py
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (6,) State [x_trolley, x_dot, phi, phi_dot, theta, theta_dot]. u : float Horizontal force on trolley [N]. d : float or None External disturbance (added to force). Default 0.
Returns¶
xdot : ndarray (6,)
Source code in opensmc/plants/crane.py
get_default_x0()
¶
get_dynamics_components(x)
¶
Decompose into affine-in-u form for each generalized coordinate.
qddot = M^{-1} * ([u;0;0] - G - C*qdot)
At the current state, returns f_vec, b_vec such that: qddot = f_vec + b_vec * u
Returns¶
f_vec : ndarray (3,) Drift terms (gravity + Coriolis, no input). b_vec : ndarray (3,) Input gain column (first column of M^{-1}).
Source code in opensmc/plants/crane.py
gravity_vector(phi, theta)
¶
Gravity vector G(q) -- (3,).
Source code in opensmc/plants/crane.py
mass_matrix(phi, theta)
¶
Mass matrix M(q) -- 3x3, symmetric, positive definite.
Source code in opensmc/plants/crane.py
DualStageNanopositioner
¶
Bases: Plant
Piezoelectric fine stage with two resonant modes.
x = [x1, x1_dot, x2, x2_dot]
- (x1, x1_dot): mode 1 displacement and velocity [m, m/s]
- (x2, x2_dot): mode 2 displacement and velocity [m, m/s]
Input: u (scalar voltage [V], clamped to [-u_max, u_max]) Output: y = x1 + x2 (total displacement [m])
Dynamics (per mode k)¶
x_k_ddot = -wn_k^2 * x_k - 2zeta_kwn_k * x_k_dot + Kp_k*wn_k^2 * u
Transfer function per mode: G_k(s) = Kp_k * wn_k^2 / (s^2 + 2zeta_kwn_k*s + wn_k^2)
Parameters¶
fn1 : float Mode 1 natural frequency [Hz] (default 780). fn2 : float Mode 2 natural frequency [Hz] (default 2340). zeta1 : float Mode 1 damping ratio (default 0.012). zeta2 : float Mode 2 damping ratio (default 0.008). Kp1 : float Mode 1 DC gain [m/V] (default 0.145e-6). Kp2 : float Mode 2 DC gain [m/V] (default 0.015e-6). u_max : float Maximum voltage magnitude [V] (default 20.0).
Source code in opensmc/plants/nanopositioner.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
Functions¶
dc_gain()
¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (4,) State [x1, x1_dot, x2, x2_dot]. u : float Input voltage [V] (clamped to [-u_max, u_max]). d : float or None Additive disturbance on voltage. Default 0.
Returns¶
xdot : ndarray (4,)
Source code in opensmc/plants/nanopositioner.py
get_default_x0()
¶
output(x)
¶
state_space_matrices()
¶
Return (A, B, C, D) state-space matrices.
Returns¶
A : ndarray (4, 4) B : ndarray (4, 1) C : ndarray (1, 4) D : ndarray (1, 1)
Source code in opensmc/plants/nanopositioner.py
InvertedPendulum
¶
Bases: Plant
Cart-pole inverted pendulum with 4 states.
States: x = [x_cart, x_cart_dot, theta, theta_dot] Input: u (scalar horizontal force on cart) Output: y = [x_cart, theta]
Dynamics¶
M_total = M + m - m * cos^2(theta)
x_cart_ddot = (u - bx_cart_dot + mltheta_dot^2sin(theta) - mgsin(theta)*cos(theta)) / M_total
theta_ddot = (-ucos(theta) + bx_cart_dotcos(theta) - mltheta_dot^2sin(theta)cos(theta) + (M+m)g*sin(theta)) / (l * M_total)
Parameters¶
M : float Cart mass [kg] (default 1.0). m : float Pendulum mass [kg] (default 0.1). l : float Pendulum half-length [m] (default 0.5). g : float Gravitational acceleration [m/s^2] (default 9.81). b : float Cart friction coefficient (default 0.1).
Source code in opensmc/plants/inverted_pendulum.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
Functions¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float x : ndarray (4,) u : float or ndarray d : float or ndarray or None
Returns¶
xdot : ndarray (4,)
Source code in opensmc/plants/inverted_pendulum.py
get_default_x0()
¶
PMSM
¶
Bases: Plant
Surface-mounted PMSM in dq-frame with 4 states.
x = [i_d, i_q, omega, theta]
- i_d, i_q: d-axis and q-axis stator currents [A]
- omega: electrical rotor speed [rad/s]
- theta: electrical rotor position [rad]
Inputs: u = [v_d, v_q] (dq-frame voltages [V], clamped to [-V_max, V_max]) Outputs: y = [omega, theta] (speed and position)
Dynamics¶
L_d * di_d/dt = v_d - R_si_d + pomegaL_qi_q L_q * di_q/dt = v_q - R_si_q - pomega(L_di_d + psi_f) J * domega/dt = T_e - B*omega - T_L dtheta/dt = omega
Electromagnetic torque: T_e = (3/2) * p * (psi_fi_q + (L_d - L_q)i_di_q) For surface-mounted (L_d = L_q): T_e = (3/2)ppsi_fi_q
Parameters¶
Rs : float Stator resistance [Ohm] (default 1.2). Ld : float d-axis inductance [H] (default 6.8e-3). Lq : float q-axis inductance [H] (default 6.8e-3). psi_f : float PM flux linkage [Wb] (default 0.175). pp : int Number of pole pairs (default 4). J : float Rotor inertia [kgm^2] (default 0.003). B : float Viscous friction coefficient [Nms/rad] (default 0.001). TL : float Nominal load torque [Nm] (default 0.0). V_max : float DC bus voltage limit [V] (default 48.0).
Source code in opensmc/plants/pmsm.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
Functions¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (4,) State [i_d, i_q, omega, theta]. u : ndarray (2,) dq-frame voltages [v_d, v_q][V]. d : float or None Load torque disturbance (added to TL). Default 0.
Returns¶
xdot : ndarray (4,)
Source code in opensmc/plants/pmsm.py
electrical_time_constant()
¶
electromagnetic_torque(i_d, i_q)
¶
get_default_x0()
¶
output(x)
¶
torque_constant()
¶
Return torque constant Kt = 1.5 * pp * psi_f [N*m/A].
Valid for surface-mounted PMSM (Ld = Lq).
Quadrotor6DOF
¶
Bases: Plant
12-state rigid-body quadrotor with Newton-Euler dynamics.
x = [x, y, z, phi, theta, psi, vx, vy, vz, p, q, r]
- (x, y, z): inertial position
- (phi, theta, psi): Euler angles (roll, pitch, yaw)
- (vx, vy, vz): inertial velocity
- (p, q, r): body angular rates
u = [U1, U2, U3, U4]
- U1: total thrust [N]
- U2: roll torque [N*m]
- U3: pitch torque [N*m]
- U4: yaw torque [N*m]
Outputs: y = [x, y, z, phi, theta, psi]
Translational dynamics¶
ax = (cos(phi)sin(theta)cos(psi) + sin(phi)sin(psi)) * U1/m ay = (cos(phi)sin(theta)sin(psi) - sin(phi)cos(psi)) * U1/m az = -g + cos(phi)*cos(theta) * U1/m
Rotational dynamics (Euler equations)¶
pdot = ((Iyy - Izz)qr + U2) / Ixx qdot = ((Izz - Ixx)pr + U3) / Iyy rdot = ((Ixx - Iyy)pq + U4) / Izz
Euler angle kinematics¶
phi_dot = p + qsin(phi)tan(theta) + rcos(phi)tan(theta) theta_dot = qcos(phi) - rsin(phi) psi_dot = (qsin(phi) + rcos(phi)) / cos(theta)
Parameters¶
mass : float Total mass [kg] (default 0.468). g : float Gravitational acceleration [m/s^2] (default 9.81). Ixx : float Roll moment of inertia [kgm^2] (default 4.856e-3). Iyy : float Pitch moment of inertia [kgm^2] (default 4.856e-3). Izz : float Yaw moment of inertia [kg*m^2] (default 8.801e-3). arm_length : float Motor arm length [m] (default 0.225).
Source code in opensmc/plants/quadrotor.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
Functions¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (12,) State vector. u : ndarray (4,) Inputs [U1, U2, U3, U4]. d : ndarray (12,) or None Additive disturbance on state derivatives. Default 0.
Returns¶
xdot : ndarray (12,)
Source code in opensmc/plants/quadrotor.py
get_default_x0()
¶
SinglePendulumCrane
¶
Bases: Plant
Single-pendulum overhead crane (Qian & Yi 2015, Eq. 2.16).
States: x = [x_trolley, x_trolley_dot, phi, phi_dot] Input: u (scalar horizontal force on trolley) [N] Output: y = [x_trolley, phi]
Dynamics¶
A_ = M + m, B_ = m * l * cos(phi) den1 = A_ * l - B_ * cos(phi) den2 = B_ * cos(phi) - A_ * l (= -den1)
x_trolley_ddot = ((u + mlphi_dot^2*sin(phi)) * l + g * B_ * sin(phi)) / den1
phi_ddot = ((u + mlphi_dot^2*sin(phi)) * cos(phi) + g * A_ * sin(phi)) / den2
Parameters¶
M : float Trolley mass [kg] (default 1.0). m : float Payload mass [kg] (default 0.8). l : float Cable length [m] (default 0.305). g : float Gravitational acceleration [m/s^2] (default 9.81).
Source code in opensmc/plants/crane.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
Functions¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (4,) State [x_trolley, x_trolley_dot, phi, phi_dot]. u : float Horizontal force on trolley [N]. d : float or None External disturbance (added to force). Default 0.
Returns¶
xdot : ndarray (4,)
Source code in opensmc/plants/crane.py
get_default_x0()
¶
get_dynamics_components(x)
¶
Decompose dynamics into affine-in-u form.
x_ddot = f1(x) + b1(x) * u phi_ddot = f2(x) + b2(x) * u
Returns¶
f1, b1, f2, b2 : float Drift and input-gain components.
Source code in opensmc/plants/crane.py
SurfaceVessel
¶
Bases: Plant
3-DOF marine surface vessel in body-fixed frame (Fossen 2011).
x = [x_n, y_n, psi, u_b, v_b, r]
- (x_n, y_n, psi): inertial (NED) position and heading [m, m, rad]
- (u_b, v_b, r): body-fixed surge, sway, yaw rate [m/s, m/s, rad/s]
tau = [tau_u, tau_v, tau_r]
- tau_u: surge force [N]
- tau_v: sway force [N]
- tau_r: yaw moment [N*m]
Outputs: y = [x_n, y_n, psi]
Dynamics¶
Kinematics: eta_dot = R(psi) * nu Kinetics: M * nu_dot = tau - C(nu)nu - D(nu)nu
where R(psi) is the 2D rotation matrix, M is the rigid-body + added mass matrix, C is the Coriolis matrix, and D contains linear + quadratic damping.
Parameters¶
m : float Vessel mass [kg] (default 23.8). Iz : float Yaw moment of inertia [kg*m^2] (default 1.76). xg : float CG offset from CO [m] (default 0.046). Xu : float Surge linear damping (default -0.72). Yv : float Sway linear damping (default -0.89). Nr : float Yaw linear damping (default -1.90). Xuu : float Surge quadratic damping (default -1.33). Yvv : float Sway quadratic damping (default -36.5). Nrr : float Yaw quadratic damping (default -0.75). Xudot : float Surge added mass (default -2.0). Yvdot : float Sway added mass (default -10.0). Nrdot : float Yaw added inertia (default -1.0).
Source code in opensmc/plants/surface_vessel.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
Functions¶
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
Parameters¶
t : float Time. x : ndarray (6,) State [x_n, y_n, psi, u_b, v_b, r]. u : ndarray (3,) Inputs [tau_u, tau_v, tau_r]. d : ndarray (3,) or None Additive disturbance forces/moment. Default 0.
Returns¶
xdot : ndarray (6,)
Source code in opensmc/plants/surface_vessel.py
get_default_x0()
¶
output(x)
¶
TwoLinkArm
¶
Bases: Plant
2-DOF planar robot manipulator (fully actuated).
x = [q1, q1_dot, q2, q2_dot]
- (q1, q1_dot): joint 1 angle and angular velocity [rad, rad/s]
- (q2, q2_dot): joint 2 angle and angular velocity [rad, rad/s]
Inputs: tau = [tau1, tau2] (joint torques [N*m]) Outputs: y = [q1, q2] (joint angles)
Dynamics¶
M(q) * qddot + C(q, qdot) * qdot + G(q) = tau + d
where M is the inertia matrix, C is the Coriolis/centrifugal vector, and G is the gravity vector.
Parameters¶
m1 : float Link 1 mass [kg] (default 1.0). m2 : float Link 2 mass [kg] (default 1.0). l1 : float Link 1 length [m] (default 1.0). l2 : float Link 2 length [m] (default 1.0). lc1 : float Link 1 center-of-mass distance [m] (default 0.5). lc2 : float Link 2 center-of-mass distance [m] (default 0.5). I1 : float Link 1 rotational inertia [kgm^2] (default 0.083). I2 : float Link 2 rotational inertia [kgm^2] (default 0.083). g : float Gravitational acceleration [m/s^2] (default 9.81). b1 : float Joint 1 viscous friction [Nms/rad] (default 0.0). b2 : float Joint 2 viscous friction [Nms/rad] (default 0.0).
Source code in opensmc/plants/two_link_arm.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | |
Functions¶
coriolis_matrix(q1, q2, q1dot, q2dot)
¶
Compute 2x2 Coriolis matrix C(q, qdot) using Christoffel symbols.
This is the unique C matrix satisfying the skew-symmetry property: Mdot - 2C is skew-symmetric (Slotine & Li, Property 7.1).
Parameters¶
q1, q2 : float Joint angles [rad]. q1dot, q2dot : float Joint angular velocities [rad/s].
Returns¶
C : ndarray (2, 2)
Source code in opensmc/plants/two_link_arm.py
coriolis_vector(q1, q2, q1dot, q2dot)
¶
Compute Coriolis/centrifugal vector C(q, qdot)*qdot.
Parameters¶
q1, q2 : float Joint angles [rad]. q1dot, q2dot : float Joint angular velocities [rad/s].
Returns¶
Cv : ndarray (2,) Coriolis/centrifugal terms.
Source code in opensmc/plants/two_link_arm.py
dynamics(t, x, u, d=None)
¶
Compute state derivatives.
M(q)qddot = tau - C(q,qdot)qdot - G(q) - friction + d
Parameters¶
t : float Time. x : ndarray (4,) State [q1, q1_dot, q2, q2_dot]. u : ndarray (2,) Joint torques [tau1, tau2]. d : ndarray (4,) or None Disturbance. If provided, d[1] and d[3] are added as torque disturbances on joints 1 and 2 respectively. Default 0.
Returns¶
xdot : ndarray (4,)
Source code in opensmc/plants/two_link_arm.py
get_default_x0()
¶
gravity_vector(q1, q2)
¶
Compute gravity vector G(q).
Parameters¶
q1, q2 : float Joint angles [rad].
Returns¶
G : ndarray (2,)
Source code in opensmc/plants/two_link_arm.py
mass_matrix(q1, q2)
¶
Compute 2x2 inertia matrix M(q).
Parameters¶
q1, q2 : float Joint angles [rad].
Returns¶
M : ndarray (2, 2) Symmetric positive-definite inertia matrix.