Skip to content

Commit 0a3e9e1

Browse files
Update Kamino Jacobians to Newton free joint wrench convention (#3707)
1 parent 825f0c9 commit 0a3e9e1

2 files changed

Lines changed: 86 additions & 60 deletions

File tree

newton/_src/solvers/kamino/_src/kinematics/jacobians.py

Lines changed: 81 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,65 @@
5959
###
6060

6161

62+
@wp.func
63+
def build_full_joint_jacobian(
64+
joint_id: wp.int32,
65+
dof_type: wp.int32,
66+
bid_B: wp.int32,
67+
bid_F: wp.int32,
68+
model_joints_X_Bj: wp.array[wp.mat33f],
69+
model_joints_X_Fj: wp.array[wp.mat33f],
70+
state_joints_p: wp.array[wp.transformf],
71+
state_bodies_q: wp.array[wp.transformf],
72+
):
73+
"""
74+
Computes the full (6x6) joint Jacobian (constraint and DoFs) for a specific joint.
75+
"""
76+
# Retrieve the pose transform of the joint
77+
T_j = state_joints_p[joint_id]
78+
r_j = wp.transform_get_translation(T_j)
79+
R_X_j = wp.quat_to_matrix(wp.transform_get_rotation(T_j))
80+
81+
# Retrieve the pose transforms of each body
82+
T_B_j = wp.transform_identity()
83+
if bid_B > -1:
84+
T_B_j = state_bodies_q[bid_B]
85+
T_F_j = state_bodies_q[bid_F]
86+
r_B_j = wp.transform_get_translation(T_B_j)
87+
r_F_j = wp.transform_get_translation(T_F_j)
88+
89+
if dof_type == JointDoFType.FREE:
90+
# By Newton's convention, a free joint's twist and wrench (specified in `joint_qd` and
91+
# `joint_f`) are both defined at the child's center of mass, with world-aligned axes. Since
92+
# Kamino avoids a conversion, the change of reference frame needs to be taken into account
93+
# in the Jacobian.
94+
JT_F_j = wp.identity(n=6, dtype=wp.float32)
95+
JT_B_j = -screw_transform_matrix_from_points(r_F_j, r_B_j)
96+
97+
else:
98+
# Compute the wrench matrices
99+
# TODO: Since the lever-arm is a relative position, can we just use B_r_Bj and F_r_Fj instead?
100+
W_j_B = screw_transform_matrix_from_points(r_j, r_B_j)
101+
W_j_F = screw_transform_matrix_from_points(r_j, r_F_j)
102+
103+
# General case: Compute the effective projector to joint frame and expand to 6D
104+
if dof_type != JointDoFType.UNIVERSAL:
105+
R_X_bar_j = expand6d(R_X_j)
106+
# Universal joint: replace R_X_j with the frame of the intermediate body for rotation constraints
107+
else:
108+
j_q_j = compute_joint_relative_quaternion(
109+
T_B_j, T_F_j, model_joints_X_Bj[joint_id], model_joints_X_Fj[joint_id]
110+
)
111+
R_intermediate = compute_intermediate_body_frame_universal_joint(j_q_j)
112+
R_X_bar_j = concat6d(R_X_j, R_X_j @ R_intermediate)
113+
114+
# Compute the extended jacobians, i.e. without the selection-matrix multiplication
115+
JT_B_j = -W_j_B @ R_X_bar_j # Reaction is on the Base body body ; (6 x 6)
116+
JT_F_j = W_j_F @ R_X_bar_j # Action is on the Follower body ; (6 x 6)
117+
118+
return JT_B_j, JT_F_j
119+
120+
62121
def make_store_joint_jacobian_dense_func(axes: Any):
63122
"""
64123
Generates a warp function to store body-pair Jacobian blocks into a target flat
@@ -472,36 +531,17 @@ def _build_joint_jacobians_dense(
472531
J_jdc_row_start = J_cjmio + nbd * (jdcgo + dyn_cts_offset_world)
473532
J_jkc_row_start = J_cjmio + nbd * (jkcgo + kin_cts_offset_world)
474533

475-
# Retrieve the pose transform of the joint
476-
T_j = state_joints_p[jid]
477-
r_j = wp.transform_get_translation(T_j)
478-
R_X_j = wp.quat_to_matrix(wp.transform_get_rotation(T_j))
479-
480-
# Retrieve the pose transforms of each body
481-
T_B_j = wp.transform_identity()
482-
if bid_B > -1:
483-
T_B_j = state_bodies_q[bid_B]
484-
T_F_j = state_bodies_q[bid_F]
485-
r_B_j = wp.transform_get_translation(T_B_j)
486-
r_F_j = wp.transform_get_translation(T_F_j)
487-
488-
# Compute the wrench matrices
489-
# TODO: Since the lever-arm is a relative position, can we just use B_r_Bj and F_r_Fj instead?
490-
W_j_B = screw_transform_matrix_from_points(r_j, r_B_j)
491-
W_j_F = screw_transform_matrix_from_points(r_j, r_F_j)
492-
493-
# General case: Compute the effective projector to joint frame and expand to 6D
494-
if dof_type != JointDoFType.UNIVERSAL:
495-
R_X_bar_j = expand6d(R_X_j)
496-
# Universal joint: replace R_X_j with the frame of the intermediate body for rotation constraints
497-
else:
498-
j_q_j = compute_joint_relative_quaternion(T_B_j, T_F_j, model_joints_X_Bj[jid], model_joints_X_Fj[jid])
499-
R_intermediate = compute_intermediate_body_frame_universal_joint(j_q_j)
500-
R_X_bar_j = concat6d(R_X_j, R_X_j @ R_intermediate)
501-
502-
# Compute the extended jacobians, i.e. without the selection-matrix multiplication
503-
JT_B_j = -W_j_B @ R_X_bar_j # Reaction is on the Base body body ; (6 x 6)
504-
JT_F_j = W_j_F @ R_X_bar_j # Action is on the Follower body ; (6 x 6)
534+
# Compute the full jacobians, i.e. without the selection-matrix multiplication
535+
JT_B_j, JT_F_j = build_full_joint_jacobian(
536+
jid,
537+
dof_type,
538+
bid_B,
539+
bid_F,
540+
model_joints_X_Bj,
541+
model_joints_X_Fj,
542+
state_joints_p,
543+
state_bodies_q,
544+
)
505545

506546
# Store joint dynamic constraint jacobians if applicable
507547
# NOTE: We use the extraction method for DoFs since dynamic constraints are in DoF-space
@@ -561,36 +601,17 @@ def _build_joint_jacobians_sparse(
561601
bid_B = model_joints_bid_B[jid]
562602
bid_F = model_joints_bid_F[jid]
563603

564-
# Retrieve the pose transform of the joint
565-
T_j = state_joints_p[jid]
566-
r_j = wp.transform_get_translation(T_j)
567-
R_X_j = wp.quat_to_matrix(wp.transform_get_rotation(T_j))
568-
569-
# Retrieve the pose transforms of each body
570-
T_B_j = wp.transform_identity()
571-
if bid_B > -1:
572-
T_B_j = state_bodies_q[bid_B]
573-
T_F_j = state_bodies_q[bid_F]
574-
r_B_j = wp.transform_get_translation(T_B_j)
575-
r_F_j = wp.transform_get_translation(T_F_j)
576-
577-
# Compute the wrench matrices
578-
# TODO: Since the lever-arm is a relative position, can we just use B_r_Bj and F_r_Fj instead?
579-
W_j_B = screw_transform_matrix_from_points(r_j, r_B_j)
580-
W_j_F = screw_transform_matrix_from_points(r_j, r_F_j)
581-
582-
# General case: Compute the effective projector to joint frame and expand to 6D
583-
if dof_type != JointDoFType.UNIVERSAL:
584-
R_X_bar_j = expand6d(R_X_j)
585-
# Universal joint: replace R_X_j with the frame of the intermediate body for rotation constraints
586-
else:
587-
j_q_j = compute_joint_relative_quaternion(T_B_j, T_F_j, model_joints_X_Bj[jid], model_joints_X_Fj[jid])
588-
R_intermediate = compute_intermediate_body_frame_universal_joint(j_q_j)
589-
R_X_bar_j = concat6d(R_X_j, R_X_j @ R_intermediate)
590-
591-
# Compute the extended jacobians, i.e. without the selection-matrix multiplication
592-
JT_B_j = -W_j_B @ R_X_bar_j # Reaction is on the Base body body ; (6 x 6)
593-
JT_F_j = W_j_F @ R_X_bar_j # Action is on the Follower body ; (6 x 6)
604+
# Compute the full jacobians, i.e. without the selection-matrix multiplication
605+
JT_B_j, JT_F_j = build_full_joint_jacobian(
606+
jid,
607+
dof_type,
608+
bid_B,
609+
bid_F,
610+
model_joints_X_Bj,
611+
model_joints_X_Fj,
612+
state_joints_p,
613+
state_bodies_q,
614+
)
594615

595616
# Store joint dynamic constraint jacobians if applicable
596617
# NOTE: We use the extraction method for DoFs since dynamic constraints are in DoF-space

newton/tests/test_body_force.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,11 @@ def test_combined_force_torque(
558558
1e-3,
559559
True,
560560
),
561+
"kamino": (
562+
newton.solvers.SolverKamino,
563+
1e-3,
564+
True,
565+
),
561566
}
562567

563568
# Test configurations for non-zero CoM tests

0 commit comments

Comments
 (0)