Note
This example is available as a jupyter notebook here.
Defining a custom Joint Type that supports dynamical simulation¤
In this notebook we will define a new joint type that is a hinge joint with a random joint axes direction.
It will also support dynamical simulation.
import ring
from ring import maths, base
import jax
import jax.numpy as jnp
import mediapy as media
from ring.algorithms.jcalc import _draw_rxyz
We will give this new joint type the identifier rr
(random revolute). Although it actually already exists in the library, but we can overwrite it.
# we use such a `params` input to specify the joint-axes, if we later then randomize the attribute of the system object
# we will have the effect of a hinge joint with a randomized joint axes direction
# here we tell the library how it should initialize this `params` PyTree
def _draw_random_joint_axis(key):
return maths.rotate(jnp.array([1.0, 0, 0]), maths.quat_random(key))
def _rr_init_joint_params(key):
return dict(joint_axes=_draw_random_joint_axis(key))
# next, we tell the library how it can randomly draw a trajectory for its generalized coordinate; the hinge joint angle
def _rr_transform(q, params):
# here we use this `params` object
axis = params["joint_axes"]
q = jnp.squeeze(q)
rot = maths.quat_rot_axis(axis, q)
return ring.Transform.create(rot=rot)
# this tells the library how to dynamically simulate the type of joint
def _motion_fn(params):
return base.Motion.create(ang=params["joint_axes"])
# now, we can put it all together into a new `x_xy.JointModel`
rr_joint = ring.JointModel(_rr_transform, motion=[_motion_fn], rcmg_draw_fn=_draw_rxyz, init_joint_params=_rr_init_joint_params)
# and then we register the joint; Note that `overwrite`=True, because it already exists; that way you can e.g. overwrite the
# default joint types such as the free joint
ring.register_new_joint_type("rr", rr_joint, q_width=1, qd_width=1, overwrite=True)
xml_str = """
<x_xy>
<options dt="0.01" gravity="0 0 9.81"></options>
<worldbody>
<geom dim="0.1" type="xyz"></geom>
<body damping=".01" joint="rr" name="pendulum" pos="0 0 0.5">
<geom dim="0.1" type="xyz"></geom>
<geom dim="0.5 0.1 0.1" mass="0.5" pos="0.25 0 0" type="box"></geom>
</body>
</worldbody>
</x_xy>
"""
# this seed determines (among other things) the randomness of the joint-axes direction
# via the above specified `_rr_init_joint_params`
seed: int = 2
sys = ring.System.create(xml_str, seed=seed)
state = ring.State.create(sys)
xs = []
for t in range(500):
state = jax.jit(ring.step)(sys, state)
xs.append(state.x)
sys.links.joint_params
def show_video(sys, xs: list[ring.Transform]):
frames = sys.render(xs, render_every_nth=4)
media.show_video(frames, fps=25)
show_video(sys, xs)
the class x_xy.RCMG
already has the built-in flag randomize_joint_params
which can be toggled in order to use the user-provided logic _rr_init_joint_params
for randomizing the joint parameters
(X, y), (key, q, x, _) = ring.RCMG(sys, randomize_joint_params=True, keep_output_extras=True).to_list()[0]
show_video(sys, x)
but for dynamic_simulation
flag to work we additional need to specify the function ring.JointModel.p_control_term
print(rr_joint.p_control_term)
try:
(X, y), (key, q, x, _) = ring.RCMG(sys, randomize_joint_params=True, keep_output_extras=True, dynamic_simulation=True).to_list()[0]
except NotImplementedError:
print("NotImplementedError: Please specify `JointModel.p_control_term` for joint type `rr`")