Rigid-body dynamics (real URDF, any robot)¶
Every other module in this package (rate_limiter, cbf_filter, trajectory,
kinematic_controller) works at the single-integrator level: you give it a joint velocity,
it gives you back a safe joint velocity. RigidBodyModel is different -- it needs an actual
physical robot description (a real URDF file) and gives you real torque-level dynamics.
from dense_armor.dynamics.urdf_dynamics import RigidBodyModel
model = RigidBodyModel("panda.urdf")
model.n
model.q_min, model.q_max, model.qd_max
model.n is the number of real, non-fixed joints the file describes -- read from the file,
not assumed. model.q_min/model.q_max/model.qd_max are each joint's real position/velocity
limit from the URDF's own <limit> tag (+/-inf wherever the URDF declares none), used by
the passivity+CBF controller to keep every command inside them.
Point RigidBodyModel at any real URDF and it builds the same thing: a mass
matrix, a gravity vector, and everything you need to simulate or control that specific robot.
The mass matrix¶
M(q) is the joint-space mass matrix at configuration q -- symmetric, positive-definite,
built from the real link masses and inertia tensors in the URDF via the standard Lagrangian
construction (kinetic energy from each link's own center-of-mass Jacobian and inertia tensor,
summed).
Full dynamics: gravity, Coriolis, and forward simulation¶
qd = jnp.zeros(model.n)
tau = jnp.zeros(model.n)
g = model.gravity_forces(q)
c_qd = model.bias_forces(q, qd)
qdd = model.forward_dynamics(q, qd, tau)
forward_dynamics solves M(q)*qdd + C(q,qd)*qd + g(q) = tau for qdd -- the real joint
acceleration a real torque command tau would produce at this state. gravity_forces and
bias_forces are available on their own too, for building a custom controller (a
gravity-compensating PD term, for instance).
Kinematics of any link, not only the end effector¶
Any link name from the URDF works -- useful for checking an elbow's position, not only the
end effector. For a link's full pose (position and orientation) and its 6xN spatial Jacobian --
needed by the full 6-DoF controller -- use link_pose and
link_spatial_jacobian instead:
p, r = model.link_pose(q, "panda_hand")
Jspatial = model.link_spatial_jacobian(q, "panda_hand") # 6xN: [angular; linear]
Xacro files and coupled joints¶
A .xacro path is expanded automatically -- see xacro support. A joint's
<mimic> tag (e.g. a gripper's two fingers tied together) is respected too -- see
coupled joints via mimic; model.n counts real independent DOF, not raw
<joint> tags.
RigidBodyModel ¶
Real Euler-Lagrange rigid-body dynamics, parsed from a real URDF file.
Parameters¶
urdf_path : str Path to a real URDF file. Any kinematic tree of revolute, continuous, prismatic, or fixed joints -- not assumed to be a single serial chain.
Attributes¶
n : int
Number of non-fixed joints (degrees of freedom), in a canonical
depth-first order from the URDF's root link.
q_min, q_max, qd_max : ndarray, shape (n,)
Real per-joint position/velocity limits from the URDF's own
Source code in dense_armor/dynamics/urdf_dynamics.py
forward_kinematics ¶
Real link poses and joint axes in world frame, as a function of q.
Returns¶
pos, rot, joint_axis_world : dict Keyed by link name (pos/rot) or joint name (joint_axis_world).
Source code in dense_armor/dynamics/urdf_dynamics.py
com_positions ¶
World-frame center-of-mass position of every link, shape (n_links, 3).
Source code in dense_armor/dynamics/urdf_dynamics.py
mass_matrix ¶
Real joint-space mass matrix M(q), shape (n, n) -- symmetric positive-definite.
Source code in dense_armor/dynamics/urdf_dynamics.py
potential_energy ¶
Real total gravitational potential energy at configuration q.
Source code in dense_armor/dynamics/urdf_dynamics.py
kinetic_energy ¶
total_energy ¶
gravity_forces ¶
bias_forces ¶
Real Coriolis/centrifugal generalized-force vector C(q,qdot)*qdot, shape (n,).
Source code in dense_armor/dynamics/urdf_dynamics.py
forward_dynamics ¶
Real joint acceleration qddot solving M(q)qddot + C(q,qdot)qdot + g(q) = tau.
Source code in dense_armor/dynamics/urdf_dynamics.py
link_position ¶
Real world-frame origin position of the named link's own frame.
link_jacobian ¶
Real translational Jacobian of the named link's own frame origin, shape (3, n).
Source code in dense_armor/dynamics/urdf_dynamics.py
link_pose ¶
Real (position, rotation matrix) of the named link's own frame, in world frame.
link_spatial_jacobian ¶
Real 6xN spatial Jacobian [angular; linear] of the named link, in world frame.
Source code in dense_armor/dynamics/urdf_dynamics.py
Details¶
Two-step promotion. Dense-Evolution-Discovery Experiment 61 built this same Euler-Lagrange
dynamics -- jax.grad/jax.jvp on the kinetic/potential energy, not hand-derived Christoffel
symbols -- but with every mass, inertia tensor, and joint origin hand-transcribed from one
specific Kinova Gen3's URDF. That was the explicit reason it wasn't promoted at the time: every
other module here is generic across any joint array, that one worked for exactly one robot.
Experiment 62 replaced the hardcoded tables with a real parser (xml.etree.ElementTree, no new
dependency) and re-validated from scratch.
Validated on three independent real robots, not one:
| robot | source | DoF | joint types |
|---|---|---|---|
| Kinova Gen3 7-DoF | the same URDF Kurtz, Wensing & Lin (2021, arXiv:2109.13349) use | 7 | all revolute |
| Kinova Gen3 6-DoF | github.com/vincekurtz/kinova_drake -- a structurally different chain | 6 | all revolute |
| Franka Emika Panda | bulletphysics/bullet3's real pybullet data -- a different manufacturer | 9 | 7 revolute + 2 prismatic |
Cross-checked against Experiment 61's own hardcoded numbers on the Gen3 7-DoF (mass matrix and gravity forces match to machine precision, 1e-16). On all three: mass matrix symmetric/positive-definite at 20 random configurations, and free (torque-free) dynamics conserve energy with the correct 4th-order RK4 convergence as the integration step shrinks -- the Panda's converges tighter (rel. drift 5.9e-7 -> 6.0e-11 -> 5.5e-15) since its published inertia tensors are simpler placeholder values, not a difference in correctness.
Mimic joints and xacro files: see their own pages, coupled joints via mimic and xacro support.
Reproducing this: pytest test/test_urdf_dynamics.py.