Numerical Dynamics and Simulation
This example connects Moro’s symbolic robot model with numerical inverse dynamics, forward dynamics and simulation.
1. Inverse and forward dynamics
Assume a fully defined planar 2R robot named robot.
import numpy as np
from moro.dynamics import forward_dynamics, inverse_dynamics
q = np.array([0.3, -0.4])
qd = np.array([0.5, -0.2])
qdd_ref = np.array([0.7, -0.6])
tau = inverse_dynamics(
robot,
q,
qd,
qdd_ref,
)
qdd_check = forward_dynamics(
robot,
q,
qd,
tau,
)
The two operations answer complementary questions:
inverse dynamics: what generalized force is required for a prescribed acceleration?;
forward dynamics: what acceleration results from a prescribed generalized force?.
For a consistent model, qdd_check should agree numerically with qdd_ref.
2. Free motion under gravity
from moro.dynamics import simulate
solution = simulate(
robot,
(0.0, 5.0),
q0=[0.4, -0.2],
qd0=[0.0, 0.0],
tau=None,
t_eval=np.linspace(0.0, 5.0, 501),
)
tau=None means zero applied generalized force. Gravity remains part of the robot model, so the system may accelerate from rest.
Inspect:
solution.q
solution.qd
solution.qdd
solution.success
solution.message
3. Time-varying generalized force
A callable can depend on time and state:
def tau(t, q, qd):
return np.array([
2.0 * np.sin(3.0 * t),
0.0,
])
solution = simulate(
robot,
(0.0, 3.0),
q0=[0.0, 0.0],
qd0=[0.0, 0.0],
tau=tau,
t_eval=np.linspace(0.0, 3.0, 301),
)
Moro passes q and qd to the callable as one-dimensional NumPy arrays.
4. Simple feedback law
The callable interface can also represent a lightweight feedback law:
Kp = np.diag([8.0, 6.0])
Kd = np.diag([3.0, 2.5])
q_ref = np.array([0.5, -0.3])
def control(t, q, qd):
return -Kp @ (q - q_ref) - Kd @ qd
This is user-defined generalized-force logic, not a dedicated controller framework.
5. Prescribed trajectory to required generalized force
Trajectory generation and dynamics remain separate:
from moro.trajectory import joint_trajectory
traj = joint_trajectory(
[0.0, 0.0],
[0.6, -0.4],
np.linspace(0.0, 2.0, 101),
method="quintic",
)
tau_history = np.array([
inverse_dynamics(robot, q, qd, qdd)
for q, qd, qdd in zip(
traj.q,
traj.qd,
traj.qdd,
)
])
The result has shape (traj.samples, robot.dof).
6. Visualization
DynamicsSolution.q follows the same time-major (N, dof) convention used by joint trajectories:
from moro.visualization import RobotVisualizer
viz = RobotVisualizer(robot)
viz.animate(solution.q)
The animation backend controls playback interval independently from the physical time vector solution.t.