Skip to content

Commit c487bc8

Browse files
[Kamino] Deduplicate FK constraint kernels (#3614)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 402d8ea commit c487bc8

2 files changed

Lines changed: 188 additions & 136 deletions

File tree

newton/_src/solvers/kamino/_src/solvers/fk/kernels.py

Lines changed: 154 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
unit_quat_conj_apply_jacobian,
2121
unit_quat_conj_to_rotation_matrix,
2222
)
23+
from ...core.types import mat34f
2324
from ...kinematics.joints import (
2425
correct_quat_vector_coord,
2526
correct_rotational_coord,
@@ -112,6 +113,127 @@ def _resolve_fk_actuation_type(act_type: wp.int32, fk_act_flag: wp.int32) -> wp.
112113
return act_type
113114

114115

116+
@wp.func
117+
def _load_joint_poses(
118+
base_id: wp.int32, follower_id: wp.int32, bodies_q: wp.array[wp.transformf]
119+
) -> tuple[wp.vec3f, wp.quatf, wp.vec3f, wp.quatf]:
120+
"""Load the base and follower poses, using the identity pose for the world."""
121+
c_base = wp.vec3f(0.0, 0.0, 0.0)
122+
q_base = wp.quatf(0.0, 0.0, 0.0, 1.0)
123+
if base_id >= 0:
124+
c_base = wp.transform_get_translation(bodies_q[base_id])
125+
q_base = wp.transform_get_rotation(bodies_q[base_id])
126+
c_follower = wp.transform_get_translation(bodies_q[follower_id])
127+
q_follower = wp.transform_get_rotation(bodies_q[follower_id])
128+
return c_base, q_base, c_follower, q_follower
129+
130+
131+
@wp.func
132+
def _get_reduced_constraint_ids(
133+
joint_id: wp.int32, ct_full_to_red_map: wp.array[wp.int32]
134+
) -> tuple[wp.vec3i, wp.vec3i]:
135+
"""Load the translational and rotational reduced constraint ids for a joint."""
136+
first_ct_id_full = 6 * joint_id
137+
trans_ct_ids_red = wp.vec3i(
138+
ct_full_to_red_map[first_ct_id_full],
139+
ct_full_to_red_map[first_ct_id_full + 1],
140+
ct_full_to_red_map[first_ct_id_full + 2],
141+
)
142+
rot_ct_ids_red = wp.vec3i(
143+
ct_full_to_red_map[first_ct_id_full + 3],
144+
ct_full_to_red_map[first_ct_id_full + 4],
145+
ct_full_to_red_map[first_ct_id_full + 5],
146+
)
147+
return trans_ct_ids_red, rot_ct_ids_red
148+
149+
150+
@wp.func
151+
def _eval_translation_jacobian_blocks(
152+
X_T: wp.mat33f,
153+
q_base: wp.quatf,
154+
q_follower: wp.quatf,
155+
x_follower: wp.vec3f,
156+
c_base: wp.vec3f,
157+
c_follower: wp.vec3f,
158+
has_base: wp.bool,
159+
) -> tuple[wp.mat33f, mat34f, wp.mat33f, mat34f]:
160+
"""Evaluate the base and follower blocks of a translational constraint Jacobian."""
161+
X_T_R_base_T = X_T * unit_quat_conj_to_rotation_matrix(q_base)
162+
jac_trans_c_base = wp.mat33f(0.0)
163+
jac_trans_q_base = mat34f(0.0)
164+
if has_base:
165+
jac_trans_c_base = -X_T_R_base_T
166+
delta_pos = unit_quat_apply(q_follower, x_follower) + c_follower - c_base
167+
jac_trans_q_base = X_T * unit_quat_conj_apply_jacobian(q_base, delta_pos)
168+
jac_trans_c_follower = X_T_R_base_T
169+
jac_trans_q_follower = X_T_R_base_T * unit_quat_apply_jacobian(q_follower, x_follower)
170+
return jac_trans_c_base, jac_trans_q_base, jac_trans_c_follower, jac_trans_q_follower
171+
172+
173+
@wp.func
174+
def _eval_rotation_jacobian_blocks(
175+
X_T: wp.mat33f,
176+
q_base: wp.quatf,
177+
q_follower: wp.quatf,
178+
q_rel_body: wp.quatf,
179+
has_base: wp.bool,
180+
) -> tuple[mat34f, mat34f]:
181+
"""Evaluate the base and follower blocks of a rotational constraint Jacobian."""
182+
q_base_sq_norm = wp.dot(q_base, q_base)
183+
q_follower_sq_norm = wp.dot(q_follower, q_follower)
184+
R_base_T = unit_quat_conj_to_rotation_matrix(q_base / wp.sqrt(q_base_sq_norm))
185+
q_rel = q_follower * wp.quat_inverse(q_rel_body) * wp.quat_inverse(q_base)
186+
temp = X_T * R_base_T * quat_left_jacobian_inverse(q_rel)
187+
jac_rot_q_base = mat34f(0.0)
188+
if has_base:
189+
jac_rot_q_base = (-2.0 / q_base_sq_norm) * temp * G_of(q_base)
190+
jac_rot_q_follower = (2.0 / q_follower_sq_norm) * temp * G_of(q_follower)
191+
return jac_rot_q_base, jac_rot_q_follower
192+
193+
194+
@wp.func
195+
def _eval_passive_universal_jacobian_blocks(
196+
X_T: wp.mat33f, q_base: wp.quatf, q_follower: wp.quatf, has_base: wp.bool
197+
) -> tuple[wp.vec4f, wp.vec4f]:
198+
"""Evaluate the base and follower blocks of a passive universal constraint Jacobian."""
199+
a_x = X_T[0]
200+
a_y = X_T[1]
201+
jac_q_base = wp.vec4f(0.0)
202+
if has_base:
203+
a_y_follower = unit_quat_apply(q_follower, a_y)
204+
jac_q_base = -a_y_follower * unit_quat_apply_jacobian(q_base, a_x)
205+
a_x_base = unit_quat_apply(q_base, a_x)
206+
jac_q_follower = -a_x_base * unit_quat_apply_jacobian(q_follower, a_y)
207+
return jac_q_base, jac_q_follower
208+
209+
210+
@wp.func
211+
def _correct_rotational_actuator_coord(
212+
actuators_q: wp.array[wp.float32], actuators_q_ref: wp.array[wp.float32], coord_id: wp.int32
213+
):
214+
"""Correct an angular actuator coordinate against its reference."""
215+
actuators_q[coord_id] = correct_rotational_coord(actuators_q[coord_id], actuators_q_ref[coord_id])
216+
217+
218+
@wp.func
219+
def _correct_quat_actuator_coords(
220+
actuators_q: wp.array[wp.float32], actuators_q_ref: wp.array[wp.float32], coord_id: wp.int32
221+
):
222+
"""Correct four quaternion actuator coordinates against their reference."""
223+
quat = wp.vec4f(
224+
actuators_q[coord_id], actuators_q[coord_id + 1], actuators_q[coord_id + 2], actuators_q[coord_id + 3]
225+
)
226+
quat_ref = wp.vec4f(
227+
actuators_q_ref[coord_id],
228+
actuators_q_ref[coord_id + 1],
229+
actuators_q_ref[coord_id + 2],
230+
actuators_q_ref[coord_id + 3],
231+
)
232+
quat_corrected = correct_quat_vector_coord(quat, quat_ref)
233+
for i in range(4):
234+
actuators_q[coord_id + i] = quat_corrected[i]
235+
236+
115237
###
116238
# Kernels
117239
###
@@ -481,15 +603,8 @@ def _eval_actuator_coords(
481603

482604
# Get base and follower transformations
483605
base_id = joints_bid_B[jt_id]
484-
if base_id < 0:
485-
c_base = wp.vec3f(0.0, 0.0, 0.0)
486-
q_base = wp.quatf(0.0, 0.0, 0.0, 1.0)
487-
else:
488-
c_base = wp.transform_get_translation(bodies_q[base_id])
489-
q_base = wp.transform_get_rotation(bodies_q[base_id])
490606
follower_id = joints_bid_F[jt_id]
491-
c_follower = wp.transform_get_translation(bodies_q[follower_id])
492-
q_follower = wp.transform_get_rotation(bodies_q[follower_id])
607+
c_base, q_base, c_follower, q_follower = _load_joint_poses(base_id, follower_id, bodies_q)
493608

494609
# Compute relative pose of follower body in joint frame of base body
495610
pos_base = c_base + wp.quat_rotate(q_base, x_base)
@@ -540,46 +655,16 @@ def _correct_actuator_coords(
540655
): # No correction needed
541656
return
542657
elif dof_type == FKJointDoFType.CYLINDRICAL: # Correct angle up to +/- 2 pi
543-
angle = actuators_q[coord_id + 1]
544-
angle_ref = actuators_q_ref[coord_id + 1]
545-
actuators_q[coord_id + 1] = correct_rotational_coord(angle, angle_ref)
658+
_correct_rotational_actuator_coord(actuators_q, actuators_q_ref, coord_id + 1)
546659
elif dof_type == FKJointDoFType.FREE: # Correct quaternion up to sign
547-
quat = wp.vec4f(
548-
actuators_q[coord_id + 3], actuators_q[coord_id + 4], actuators_q[coord_id + 5], actuators_q[coord_id + 6]
549-
)
550-
quat_ref = wp.vec4f(
551-
actuators_q_ref[coord_id + 3],
552-
actuators_q_ref[coord_id + 4],
553-
actuators_q_ref[coord_id + 5],
554-
actuators_q_ref[coord_id + 6],
555-
)
556-
quat_corrected = correct_quat_vector_coord(quat, quat_ref)
557-
for i in range(4):
558-
actuators_q[coord_id + 3 + i] = quat_corrected[i]
660+
_correct_quat_actuator_coords(actuators_q, actuators_q_ref, coord_id + 3)
559661
elif dof_type == FKJointDoFType.REVOLUTE: # Correct angle up to +/- 2 pi
560-
angle = actuators_q[coord_id]
561-
angle_ref = actuators_q_ref[coord_id]
562-
actuators_q[coord_id] = correct_rotational_coord(angle, angle_ref)
662+
_correct_rotational_actuator_coord(actuators_q, actuators_q_ref, coord_id)
563663
elif dof_type == FKJointDoFType.SPHERICAL: # Correct quaternion up to sign
564-
quat = wp.vec4f(
565-
actuators_q[coord_id], actuators_q[coord_id + 1], actuators_q[coord_id + 2], actuators_q[coord_id + 3]
566-
)
567-
quat_ref = wp.vec4f(
568-
actuators_q_ref[coord_id],
569-
actuators_q_ref[coord_id + 1],
570-
actuators_q_ref[coord_id + 2],
571-
actuators_q_ref[coord_id + 3],
572-
)
573-
quat_corrected = correct_quat_vector_coord(quat, quat_ref)
574-
for i in range(4):
575-
actuators_q[coord_id + i] = quat_corrected[i]
664+
_correct_quat_actuator_coords(actuators_q, actuators_q_ref, coord_id)
576665
elif dof_type == FKJointDoFType.UNIVERSAL: # Correct angles up to +/- 2 pi
577-
angle = actuators_q[coord_id]
578-
angle_ref = actuators_q_ref[coord_id]
579-
actuators_q[coord_id] = correct_rotational_coord(angle, angle_ref)
580-
angle = actuators_q[coord_id + 1]
581-
angle_ref = actuators_q_ref[coord_id + 1]
582-
actuators_q[coord_id + 1] = correct_rotational_coord(angle, angle_ref)
666+
_correct_rotational_actuator_coord(actuators_q, actuators_q_ref, coord_id)
667+
_correct_rotational_actuator_coord(actuators_q, actuators_q_ref, coord_id + 1)
583668
else:
584669
assert False, "Unexpected actuator dof type" # noqa: B011
585670

@@ -929,17 +1014,7 @@ def _eval_joint_constraints(
9291014
jt_id_tot = first_joint_id[wd_id] + jt_id_loc
9301015

9311016
# Get reduced constraint ids (-1 meaning constraint is not used)
932-
first_ct_id_full = 6 * jt_id_tot
933-
trans_ct_ids_red = wp.vec3i(
934-
ct_full_to_red_map[first_ct_id_full],
935-
ct_full_to_red_map[first_ct_id_full + 1],
936-
ct_full_to_red_map[first_ct_id_full + 2],
937-
)
938-
rot_ct_ids_red = wp.vec3i(
939-
ct_full_to_red_map[first_ct_id_full + 3],
940-
ct_full_to_red_map[first_ct_id_full + 4],
941-
ct_full_to_red_map[first_ct_id_full + 5],
942-
)
1017+
trans_ct_ids_red, rot_ct_ids_red = _get_reduced_constraint_ids(jt_id_tot, ct_full_to_red_map)
9431018

9441019
# Get joint local positions and orientation
9451020
x_base = joints_B_r_B[jt_id_tot]
@@ -948,15 +1023,8 @@ def _eval_joint_constraints(
9481023

9491024
# Get base and follower transformations
9501025
base_id = joints_bid_B[jt_id_tot]
951-
if base_id < 0:
952-
c_base = wp.vec3f(0.0, 0.0, 0.0)
953-
q_base = wp.quatf(0.0, 0.0, 0.0, 1.0)
954-
else:
955-
c_base = wp.transform_get_translation(bodies_q[base_id])
956-
q_base = wp.transform_get_rotation(bodies_q[base_id])
9571026
follower_id = joints_bid_F[jt_id_tot]
958-
c_follower = wp.transform_get_translation(bodies_q[follower_id])
959-
q_follower = wp.transform_get_rotation(bodies_q[follower_id])
1027+
c_base, q_base, c_follower, q_follower = _load_joint_poses(base_id, follower_id, bodies_q)
9601028

9611029
# Get target relative transformation, in joint/body frame for translation/rotation part
9621030
t_rel_joint = wp.transform_get_translation(target_rel_transforms[jt_id_tot])
@@ -1141,57 +1209,32 @@ def _eval_joint_constraints_jacobian(
11411209
jt_id_tot = first_joint_id[wd_id] + jt_id_loc
11421210

11431211
# Get reduced constraint ids (-1 meaning constraint is not used)
1144-
first_ct_id_full = 6 * jt_id_tot
1145-
trans_ct_ids_red = wp.vec3i(
1146-
ct_full_to_red_map[first_ct_id_full],
1147-
ct_full_to_red_map[first_ct_id_full + 1],
1148-
ct_full_to_red_map[first_ct_id_full + 2],
1149-
)
1150-
rot_ct_ids_red = wp.vec3i(
1151-
ct_full_to_red_map[first_ct_id_full + 3],
1152-
ct_full_to_red_map[first_ct_id_full + 4],
1153-
ct_full_to_red_map[first_ct_id_full + 5],
1154-
)
1212+
trans_ct_ids_red, rot_ct_ids_red = _get_reduced_constraint_ids(jt_id_tot, ct_full_to_red_map)
11551213

11561214
# Get joint local positions and orientation
11571215
x_follower = joints_F_r_F[jt_id_tot]
11581216
X_T = wp.transpose(joints_X_Bj[jt_id_tot])
11591217

11601218
# Get base and follower transformations
11611219
base_id_tot = joints_bid_B[jt_id_tot]
1162-
if base_id_tot < 0:
1163-
c_base = wp.vec3f(0.0, 0.0, 0.0)
1164-
q_base = wp.quatf(0.0, 0.0, 0.0, 1.0)
1165-
else:
1166-
c_base = wp.transform_get_translation(bodies_q[base_id_tot])
1167-
q_base = wp.transform_get_rotation(bodies_q[base_id_tot])
11681220
follower_id_tot = joints_bid_F[jt_id_tot]
1169-
c_follower = wp.transform_get_translation(bodies_q[follower_id_tot])
1170-
q_follower = wp.transform_get_rotation(bodies_q[follower_id_tot])
1221+
c_base, q_base, c_follower, q_follower = _load_joint_poses(base_id_tot, follower_id_tot, bodies_q)
11711222
base_id_loc = base_id_tot - first_body_id[wd_id]
11721223
follower_id_loc = follower_id_tot - first_body_id[wd_id]
11731224

11741225
# Get target relative transformation (rotation part only, as translation part doesn't affect the Jacobian)
11751226
q_rel_body = wp.transform_get_rotation(target_rel_transforms[jt_id_tot])
11761227

11771228
# Translation constraints
1178-
X_T_R_base_T = X_T * unit_quat_conj_to_rotation_matrix(q_base)
1179-
if base_id_tot >= 0:
1180-
jac_trans_c_base = -X_T_R_base_T
1181-
delta_pos = unit_quat_apply(q_follower, x_follower) + c_follower - c_base
1182-
jac_trans_q_base = X_T * unit_quat_conj_apply_jacobian(q_base, delta_pos)
1183-
jac_trans_c_follower = X_T_R_base_T
1184-
jac_trans_q_follower = X_T_R_base_T * unit_quat_apply_jacobian(q_follower, x_follower)
1185-
1229+
jac_trans_c_base, jac_trans_q_base, jac_trans_c_follower, jac_trans_q_follower = (
1230+
_eval_translation_jacobian_blocks(
1231+
X_T, q_base, q_follower, x_follower, c_base, c_follower, base_id_tot >= 0
1232+
)
1233+
)
11861234
# Rotation constraints
1187-
q_base_sq_norm = wp.dot(q_base, q_base)
1188-
q_follower_sq_norm = wp.dot(q_follower, q_follower)
1189-
R_base_T = unit_quat_conj_to_rotation_matrix(q_base / wp.sqrt(q_base_sq_norm))
1190-
q_rel = q_follower * wp.quat_inverse(q_rel_body) * wp.quat_inverse(q_base)
1191-
temp = X_T * R_base_T * quat_left_jacobian_inverse(q_rel)
1192-
if base_id_tot >= 0:
1193-
jac_rot_q_base = (-2.0 / q_base_sq_norm) * temp * G_of(q_base)
1194-
jac_rot_q_follower = (2.0 / q_follower_sq_norm) * temp * G_of(q_follower)
1235+
jac_rot_q_base, jac_rot_q_follower = _eval_rotation_jacobian_blocks(
1236+
X_T, q_base, q_follower, q_rel_body, base_id_tot >= 0
1237+
)
11951238
# Note: we need X^T * R_base^T both for translation and rotation constraints, but to get the correct
11961239
# derivatives for non-unit quaternions (which may be encountered before convergence) we end up needing
11971240
# to use a separate formula to evaluate R_base in either case
@@ -1228,13 +1271,9 @@ def _eval_joint_constraints_jacobian(
12281271
return
12291272

12301273
# Compute constraint Jacobian (cross product between x axis on base and y axis on follower)
1231-
a_x = X_T[0]
1232-
a_y = X_T[1]
1233-
if base_id_tot >= 0:
1234-
a_y_follower = unit_quat_apply(q_follower, a_y)
1235-
jac_q_base = -a_y_follower * unit_quat_apply_jacobian(q_base, a_x)
1236-
a_x_base = unit_quat_apply(q_base, a_x)
1237-
jac_q_follower = -a_x_base * unit_quat_apply_jacobian(q_follower, a_y)
1274+
jac_q_base, jac_q_follower = _eval_passive_universal_jacobian_blocks(
1275+
X_T, q_base, q_follower, base_id_tot >= 0
1276+
)
12381277

12391278
# Write out Jacobian
12401279
for i in range(4):
@@ -1319,37 +1358,20 @@ def _eval_joint_constraints_sparse_jacobian(
13191358

13201359
# Get base and follower transformations
13211360
base_id = joints_bid_B[jt_id_tot]
1322-
if base_id < 0:
1323-
c_base = wp.vec3f(0.0, 0.0, 0.0)
1324-
q_base = wp.quatf(0.0, 0.0, 0.0, 1.0)
1325-
else:
1326-
c_base = wp.transform_get_translation(bodies_q[base_id])
1327-
q_base = wp.transform_get_rotation(bodies_q[base_id])
13281361
follower_id = joints_bid_F[jt_id_tot]
1329-
c_follower = wp.transform_get_translation(bodies_q[follower_id])
1330-
q_follower = wp.transform_get_rotation(bodies_q[follower_id])
1362+
c_base, q_base, c_follower, q_follower = _load_joint_poses(base_id, follower_id, bodies_q)
13311363

13321364
# Get target relative transformation (rotation part only, as translation part doesn't affect the Jacobian)
13331365
q_rel_body = wp.transform_get_rotation(target_rel_transforms[jt_id_tot])
13341366

13351367
# Translation constraints
1336-
X_T_R_base_T = X_T * unit_quat_conj_to_rotation_matrix(q_base)
1337-
if base_id >= 0:
1338-
jac_trans_c_base = -X_T_R_base_T
1339-
delta_pos = unit_quat_apply(q_follower, x_follower) + c_follower - c_base
1340-
jac_trans_q_base = X_T * unit_quat_conj_apply_jacobian(q_base, delta_pos)
1341-
jac_trans_c_follower = X_T_R_base_T
1342-
jac_trans_q_follower = X_T_R_base_T * unit_quat_apply_jacobian(q_follower, x_follower)
1343-
1368+
jac_trans_c_base, jac_trans_q_base, jac_trans_c_follower, jac_trans_q_follower = (
1369+
_eval_translation_jacobian_blocks(X_T, q_base, q_follower, x_follower, c_base, c_follower, base_id >= 0)
1370+
)
13441371
# Rotation constraints
1345-
q_base_sq_norm = wp.dot(q_base, q_base)
1346-
q_follower_sq_norm = wp.dot(q_follower, q_follower)
1347-
R_base_T = unit_quat_conj_to_rotation_matrix(q_base / wp.sqrt(q_base_sq_norm))
1348-
q_rel = q_follower * wp.quat_inverse(q_rel_body) * wp.quat_inverse(q_base)
1349-
temp = X_T * R_base_T * quat_left_jacobian_inverse(q_rel)
1350-
if base_id >= 0:
1351-
jac_rot_q_base = (-2.0 / q_base_sq_norm) * temp * G_of(q_base)
1352-
jac_rot_q_follower = (2.0 / q_follower_sq_norm) * temp * G_of(q_follower)
1372+
jac_rot_q_base, jac_rot_q_follower = _eval_rotation_jacobian_blocks(
1373+
X_T, q_base, q_follower, q_rel_body, base_id >= 0
1374+
)
13531375
# Note: we need X^T * R_base^T both for translation and rotation constraints, but to get the correct
13541376
# derivatives for non-unit quaternions (which may be encountered before convergence) we end up needing
13551377
# to use a separate formula to evaluate R_base in either case
@@ -1390,13 +1412,9 @@ def _eval_joint_constraints_sparse_jacobian(
13901412
return
13911413

13921414
# Compute constraint Jacobian (cross product between x axis on base and y axis on follower)
1393-
a_x = X_T[0]
1394-
a_y = X_T[1]
1395-
if base_id >= 0:
1396-
a_y_follower = unit_quat_apply(q_follower, a_y)
1397-
jac_q_base = -a_y_follower * unit_quat_apply_jacobian(q_base, a_x)
1398-
a_x_base = unit_quat_apply(q_base, a_x)
1399-
jac_q_follower = -a_x_base * unit_quat_apply_jacobian(q_follower, a_y)
1415+
jac_q_base, jac_q_follower = _eval_passive_universal_jacobian_blocks(
1416+
X_T, q_base, q_follower, base_id >= 0
1417+
)
14001418

14011419
# Write out Jacobian
14021420
if base_id >= 0:

0 commit comments

Comments
 (0)