Skip to content

Commit 92649c9

Browse files
Ruben Grandiacursoragent
andauthored
[Kamino] Implement joint effort limits (#3990)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1c248d3 commit 92649c9

54 files changed

Lines changed: 3025 additions & 612 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

asv/benchmarks/simulation/bench_kamino.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,15 @@ def _collect_metrics(self):
120120

121121

122122
class FastDRLegs(_FastBenchmark):
123+
version = "2" # effort limits now enforced for implicit PD (#3990) -> new ASV series
123124
num_frames = 25
124125
robot = "dr_legs"
125126
repeat = 2
126127
world_count = 32
127128

128129

129130
class FastMetricsDRLegs(_SimulationMetricTracksUnparameterized):
131+
version = "2" # effort limits now enforced for implicit PD (#3990) -> new ASV series
130132
num_frames = 25
131133
robot = "dr_legs"
132134
samples = 2
@@ -143,6 +145,7 @@ def setup_cache(self):
143145

144146

145147
class KpiDRLegs(_KpiBenchmark):
148+
version = "2" # effort limits now enforced for implicit PD (#3990) -> new ASV series
146149
params: ClassVar[list[list[int]]] = [[4096]]
147150
num_frames = 25
148151
robot = "dr_legs"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enforce joint effort limits in SolverKamino for explicit commands and implicit PD drives.

docs/solvers/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ constraints, with opt-in unified compliant ALM and a deprecated legacy AVBD path
334334
- |no|
335335
- |yes|
336336
- |no|
337-
- |no|
337+
- |yes|
338338
* - :attr:`~newton.Model.joint_velocity_limit`
339339
- |no|
340340
- |no|

docs/solvers/kamino.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,21 @@ For large bilateral systems, opt into RCM-reordered factorization explicitly:
7171
The cached permutation remains mathematically valid when matrix values or
7272
sparsity change and is recomputed automatically if the active dimension
7373
changes. Keep the default ``"LLTB"`` solver for small systems.
74+
75+
Actuation and forward kinematics
76+
--------------------------------
77+
78+
Kamino dynamics routes actuation independently for each joint DoF. A DoF can
79+
use explicit effort, unbounded implicit PD, or effort-limited implicit PD;
80+
passive armature, damping, and Coulomb friction are likewise configured per
81+
DoF. Implicit-PD target modes require a non-zero applicable gain: velocity
82+
mode requires derivative gain, while position-based modes require proportional
83+
or derivative gain. Coulomb friction supports all non-free joint types, while
84+
joint dynamics and implicit PD currently support revolute, prismatic, and
85+
gimbal joint types only.
86+
87+
The forward-kinematics solver still partitions each joint as entirely passive
88+
or entirely actuated. Different non-passive target modes are allowed within a
89+
joint, but mixing passive and actuated DoFs within one joint is not yet
90+
supported. The ``fk_actuation_flag`` model attribute provides an explicit
91+
joint-level override for this FK partition.

newton/_src/solvers/kamino/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ It currently supports:
2121
- A large set of common and advanced bilateral joint constraints
2222
- Unilateral joint-limit, and contact constraints with spatial friction and restitutive impacts
2323
- Joint Coulomb friction
24+
- Bounded joint effort for explicit commands and implicit PD drives
2425
- Fully configurable constraint stabilization that can be specified per constraint subset
2526
- Hard joint-limit and contact constraints enforced via an advanced Proximal-ADMM forward dynamics solver
2627

newton/_src/solvers/kamino/_src/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
convert_model_materials,
2121
validate_model_structural_updates,
2222
)
23-
from .core.joints import JOINT_QMAX, JOINT_QMIN, JointActuationType
23+
from .core.joints import JOINT_QMAX, JOINT_QMIN, DofActuationPath, JointActuationType
2424
from .core.model import ModelKamino
2525
from .core.state import StateKamino
2626
from .geometry.contacts import (
@@ -42,6 +42,7 @@
4242
"CollisionDetector",
4343
"ContactsKamino",
4444
"ControlKamino",
45+
"DofActuationPath",
4546
"JointActuationType",
4647
"ModelKamino",
4748
"SolverKaminoImpl",

newton/_src/solvers/kamino/_src/core/builder.py

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def __init__(self, default_world: bool = False):
8989
self._num_joint_dynamic_cts: int = 0
9090
self._num_joint_bounded_cts: int = 0
9191
self._num_joint_friction_cts: int = 0
92+
self._num_joint_effort_cts: int = 0
9293

9394
# Contact capacity settings
9495
self._max_contacts_per_pair: int | None = None
@@ -205,6 +206,11 @@ def num_friction_joint_cts(self) -> int:
205206
"""Returns the number of Coulomb joint friction constraint rows contained in the model."""
206207
return self._num_joint_friction_cts
207208

209+
@property
210+
def num_effort_joint_cts(self) -> int:
211+
"""Returns the number of effort-limit implicit-PD constraint rows contained in the model."""
212+
return self._num_joint_effort_cts
213+
208214
@property
209215
def worlds(self) -> list[WorldDescriptor]:
210216
"""Returns the list of world descriptors contained in the model."""
@@ -396,7 +402,7 @@ def add_rigid_body_descriptor(self, body: RigidBodyDescriptor, world_index: int
396402

397403
def add_joint(
398404
self,
399-
act_type: JointActuationType,
405+
act_type: JointActuationType | list[JointActuationType],
400406
dof_type: JointDoFType,
401407
bid_B: int,
402408
bid_F: int,
@@ -421,7 +427,8 @@ def add_joint(
421427
Add a joint entity to the model using explicit specifications.
422428
423429
Args:
424-
act_type: The actuation type of the joint.
430+
act_type: Actuation type applied to every DoF, or the actuation
431+
type of each DoF.
425432
dof_type: The degree of freedom type of the joint.
426433
bid_B: The index of the body on the "base" side of the joint.
427434
bid_F: The index of the body on the "follower" side of the joint.
@@ -446,13 +453,15 @@ def add_joint(
446453
Returns:
447454
The index of the newly added joint.
448455
"""
449-
# Check if the actuation type is valid
450-
if not isinstance(act_type, JointActuationType):
451-
raise TypeError(f"Invalid actuation type: {act_type}. Must be `JointActuationType`.")
452-
453456
# Check if the DoF type is valid
454457
if not isinstance(dof_type, JointDoFType):
455458
raise TypeError(f"Invalid DoF type: {dof_type}. Must be `JointDoFType`.")
459+
if isinstance(act_type, JointActuationType):
460+
dof_act_types = [act_type] * dof_type.num_dofs
461+
elif isinstance(act_type, list):
462+
dof_act_types = act_type
463+
else:
464+
raise TypeError(f"Invalid actuation type: {act_type}. Must be `JointActuationType` or a list of them.")
456465

457466
# Create a joint descriptor from the provided specifications
458467
# NOTE: Specifying a name is required by the base descriptor class,
@@ -461,7 +470,7 @@ def add_joint(
461470
joint = JointDescriptor(
462471
name=name if name is not None else f"joint_{self._num_joints}",
463472
uid=uid,
464-
act_type=act_type,
473+
dof_act_types=dof_act_types,
465474
dof_type=dof_type,
466475
bid_B=bid_B,
467476
bid_F=bid_F,
@@ -523,6 +532,7 @@ def add_joint_descriptor(self, joint: JointDescriptor, world_index: int = 0) ->
523532
self._num_joint_kinematic_cts += joint.num_kinematic_cts
524533
self._num_joint_bounded_cts += joint.num_bounded_cts
525534
self._num_joint_friction_cts += joint.num_friction_cts
535+
self._num_joint_effort_cts += joint.num_effort_cts
526536

527537
# Return the new joint index
528538
return joint.jid
@@ -744,6 +754,7 @@ def add_builder(self, other: ModelBuilderKamino):
744754
self._num_joint_kinematic_cts += world.num_kinematic_joint_cts
745755
self._num_joint_bounded_cts += world.num_bounded_joint_cts
746756
self._num_joint_friction_cts += world.num_friction_joint_cts
757+
self._num_joint_effort_cts += world.num_effort_joint_cts
747758

748759
# Update the number of worlds
749760
self._num_worlds += other._num_worlds
@@ -982,6 +993,7 @@ def finalize(
982993
info_njkc = []
983994
info_nbc = []
984995
info_nfc = []
996+
info_nec = []
985997
info_bio = []
986998
info_jio = []
987999
info_gio = []
@@ -997,6 +1009,7 @@ def finalize(
9971009
info_jkcio = []
9981010
info_jbcio = []
9991011
info_jfcio = []
1012+
info_jecio = []
10001013
info_base_bid = []
10011014
info_base_jid = []
10021015

@@ -1019,8 +1032,9 @@ def finalize(
10191032
joints_label = []
10201033
joints_wid = []
10211034
joints_jid = []
1022-
joints_dofid = []
1023-
joints_actid = []
1035+
joints_dof_type = []
1036+
joints_act_type = []
1037+
joints_dof_act_types = []
10241038
joints_fk_act_flag = []
10251039
joints_q_j_0 = []
10261040
joints_dq_j_0 = []
@@ -1045,7 +1059,12 @@ def finalize(
10451059
joints_nkincts_j = []
10461060
joints_nbccts_j = []
10471061
joints_nfriccts_j = []
1062+
joints_neffortcts_j = []
10481063
joints_ndyncts_j = []
1064+
joints_dof_act_paths = []
1065+
joints_dynamic_cts_axis = []
1066+
joints_friction_cts_axis = []
1067+
joints_effort_cts_axis = []
10491068
joints_q_start = []
10501069
joints_dq_start = []
10511070
joints_pq_start = []
@@ -1057,6 +1076,7 @@ def finalize(
10571076
joints_kcts_start = []
10581077
joints_bcts_start = []
10591078
joints_fcts_start = []
1079+
joints_ects_start = []
10601080

10611081
# Initialize the collision geometry data collections
10621082
geoms_label = []
@@ -1105,6 +1125,7 @@ def collect_model_info_data():
11051125
info_njkc.append(world.num_kinematic_joint_cts)
11061126
info_nbc.append(world.num_bounded_joint_cts)
11071127
info_nfc.append(world.num_friction_joint_cts)
1128+
info_nec.append(world.num_effort_joint_cts)
11081129
info_bio.append(world.bodies_idx_offset)
11091130
info_jio.append(world.joints_idx_offset)
11101131
info_gio.append(world.geoms_idx_offset)
@@ -1123,6 +1144,7 @@ def collect_model_info_data():
11231144
info_jkcio.append(world.joint_kinematic_cts_idx_offset)
11241145
info_jbcio.append(world.joint_bounded_cts_idx_offset)
11251146
info_jfcio.append(world.joint_friction_cts_idx_offset)
1147+
info_jecio.append(world.joint_effort_cts_idx_offset)
11261148
info_base_bid.append((world.base_body_idx + world.bodies_idx_offset) if world.has_base_body else -1)
11271149
info_base_jid.append((world.base_joint_idx + world.joints_idx_offset) if world.has_base_joint else -1)
11281150

@@ -1153,8 +1175,10 @@ def collect_joint_model_data():
11531175
joints_label.append(joint.name)
11541176
joints_wid.append(joint.wid)
11551177
joints_jid.append(joint.jid)
1156-
joints_dofid.append(joint.dof_type.value)
1157-
joints_actid.append(joint.act_type.value)
1178+
joints_dof_type.append(joint.dof_type.value)
1179+
joints_act_type.append(joint.act_type.value)
1180+
joints_dof_act_types.extend(act_type.value for act_type in joint.dof_act_types)
1181+
joints_dof_act_paths.extend(path.value for path in joint.dof_act_paths())
11581182
joints_fk_act_flag.append(joint.fk_act_flag)
11591183
joints_B_r_Bj.append(joint.B_r_Bj)
11601184
joints_F_r_Fj.append(joint.F_r_Fj)
@@ -1193,6 +1217,10 @@ def collect_joint_model_data():
11931217
joints_nkincts_j.append(joint.num_kinematic_cts)
11941218
joints_nbccts_j.append(joint.num_bounded_cts)
11951219
joints_nfriccts_j.append(joint.num_friction_cts)
1220+
joints_neffortcts_j.append(joint.num_effort_cts)
1221+
joints_dynamic_cts_axis.extend(joint.dynamic_cts_axes())
1222+
joints_friction_cts_axis.extend(joint.friction_cts_axes())
1223+
joints_effort_cts_axis.extend(joint.effort_cts_axes())
11961224
joints_q_start.append(joint.coords_offset + world.joint_coords_idx_offset)
11971225
joints_dq_start.append(joint.dofs_offset + world.joint_dofs_idx_offset)
11981226
joints_pq_start.append(joint.passive_coords_offset + world.joint_passive_coords_idx_offset)
@@ -1204,6 +1232,7 @@ def collect_joint_model_data():
12041232
joints_kcts_start.append(joint.kinematic_cts_offset + world.joint_kinematic_cts_idx_offset)
12051233
joints_bcts_start.append(joint.bounded_cts_offset + world.joint_bounded_cts_idx_offset)
12061234
joints_fcts_start.append(joint.friction_cts_offset + world.joint_friction_cts_idx_offset)
1235+
joints_ects_start.append(joint.effort_cts_offset + world.joint_effort_cts_idx_offset)
12071236
joints_bid_B.append(joint.bid_B + world_bio if joint.bid_B >= 0 else -1)
12081237
joints_bid_F.append(joint.bid_F + world_bio if joint.bid_F >= 0 else -1)
12091238

@@ -1219,6 +1248,7 @@ def collect_joint_model_data():
12191248
joints_kcts_start.append(self._num_joint_kinematic_cts)
12201249
joints_bcts_start.append(self._num_joint_bounded_cts)
12211250
joints_fcts_start.append(self._num_joint_friction_cts)
1251+
joints_ects_start.append(self._num_joint_effort_cts)
12221252

12231253
# A helper function to collect model collision geometries data
12241254
def collect_geometry_model_data():
@@ -1317,6 +1347,8 @@ def collect_material_pairs_model_data():
13171347
max_of_num_bounded_joint_cts=max([world.num_bounded_joint_cts for world in self._worlds]),
13181348
sum_of_num_friction_joint_cts=self._num_joint_friction_cts,
13191349
max_of_num_friction_joint_cts=max([world.num_friction_joint_cts for world in self._worlds]),
1350+
sum_of_num_effort_joint_cts=self._num_joint_effort_cts,
1351+
max_of_num_effort_joint_cts=max([world.num_effort_joint_cts for world in self._worlds]),
13201352
# Initialize inequality entity counts to zero
13211353
sum_of_max_limits=0,
13221354
max_of_max_limits=0,
@@ -1383,6 +1415,7 @@ def collect_material_pairs_model_data():
13831415
num_joint_kinematic_cts=to_warp_int32_array(info_njkc),
13841416
num_joint_bounded_cts=to_warp_int32_array(info_nbc),
13851417
num_joint_friction_cts=to_warp_int32_array(info_nfc),
1418+
num_joint_effort_cts=to_warp_int32_array(info_nec),
13861419
bodies_offset=to_warp_int32_array(info_bio),
13871420
joints_offset=to_warp_int32_array(info_jio),
13881421
geoms_offset=to_warp_int32_array(info_gio),
@@ -1398,6 +1431,7 @@ def collect_material_pairs_model_data():
13981431
joint_kinematic_cts_offset=to_warp_int32_array(info_jkcio),
13991432
joint_bounded_cts_offset=to_warp_int32_array(info_jbcio),
14001433
joint_friction_cts_offset=to_warp_int32_array(info_jfcio),
1434+
joint_effort_cts_offset=to_warp_int32_array(info_jecio),
14011435
base_body_index=to_warp_int32_array(info_base_bid),
14021436
base_joint_index=to_warp_int32_array(info_base_jid),
14031437
has_world_without_base_body=has_world_without_base_body,
@@ -1432,8 +1466,10 @@ def collect_material_pairs_model_data():
14321466
label=joints_label,
14331467
wid=to_warp_int32_array(joints_wid),
14341468
jid=to_warp_int32_array(joints_jid),
1435-
dof_type=to_warp_int32_array(joints_dofid),
1436-
act_type=to_warp_int32_array(joints_actid),
1469+
dof_type=to_warp_int32_array(joints_dof_type),
1470+
act_type=to_warp_int32_array(joints_act_type),
1471+
dof_act_types=to_warp_int32_array(joints_dof_act_types),
1472+
dof_act_paths=to_warp_int32_array(joints_dof_act_paths),
14371473
fk_act_flag=to_warp_int32_array(joints_fk_act_flag)
14381474
if any(act_flag != -1 for act_flag in joints_fk_act_flag)
14391475
else None,
@@ -1461,6 +1497,7 @@ def collect_material_pairs_model_data():
14611497
num_kinematic_cts=to_warp_int32_array(joints_nkincts_j),
14621498
num_bounded_cts=to_warp_int32_array(joints_nbccts_j),
14631499
num_friction_cts=to_warp_int32_array(joints_nfriccts_j),
1500+
num_effort_cts=to_warp_int32_array(joints_neffortcts_j),
14641501
coords_offset=to_warp_int32_array(joints_q_start),
14651502
dofs_offset=to_warp_int32_array(joints_dq_start),
14661503
passive_coords_offset=to_warp_int32_array(joints_pq_start),
@@ -1472,6 +1509,10 @@ def collect_material_pairs_model_data():
14721509
kinematic_cts_offset=to_warp_int32_array(joints_kcts_start),
14731510
bounded_cts_offset=to_warp_int32_array(joints_bcts_start),
14741511
friction_cts_offset=to_warp_int32_array(joints_fcts_start),
1512+
effort_cts_offset=to_warp_int32_array(joints_ects_start),
1513+
dynamic_cts_axis=to_warp_int32_array(joints_dynamic_cts_axis),
1514+
friction_cts_axis=to_warp_int32_array(joints_friction_cts_axis),
1515+
effort_cts_axis=to_warp_int32_array(joints_effort_cts_axis),
14751516
)
14761517

14771518
# Create the collision geometries model
@@ -1864,6 +1905,7 @@ def _compute_world_offsets(self):
18641905
joint_kinematic_cts_idx_offset: int = 0
18651906
joint_bounded_cts_idx_offset: int = 0
18661907
joint_friction_cts_idx_offset: int = 0
1908+
joint_effort_cts_idx_offset: int = 0
18671909
# Iterate over each world and set their model offsets
18681910
for world in self._worlds:
18691911
# Set the offsets in the world descriptor to the current values
@@ -1882,6 +1924,7 @@ def _compute_world_offsets(self):
18821924
world.joint_kinematic_cts_idx_offset = int(joint_kinematic_cts_idx_offset)
18831925
world.joint_bounded_cts_idx_offset = int(joint_bounded_cts_idx_offset)
18841926
world.joint_friction_cts_idx_offset = int(joint_friction_cts_idx_offset)
1927+
world.joint_effort_cts_idx_offset = int(joint_effort_cts_idx_offset)
18851928
# Update the offsets for the next world
18861929
bodies_idx_offset += world.num_bodies
18871930
joints_idx_offset += world.num_joints
@@ -1898,6 +1941,7 @@ def _compute_world_offsets(self):
18981941
joint_kinematic_cts_idx_offset += world.num_kinematic_joint_cts
18991942
joint_bounded_cts_idx_offset += world.num_bounded_joint_cts
19001943
joint_friction_cts_idx_offset += world.num_friction_joint_cts
1944+
joint_effort_cts_idx_offset += world.num_effort_joint_cts
19011945

19021946
def _collect_geom_max_contact_hints(self) -> tuple[int, list[int]]:
19031947
"""

0 commit comments

Comments
 (0)