Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
embodichain.toolkits.dynamics_calibration
=========================================

The dynamics-calibration package provides application-level effective drive
search, isolated evaluation, qualification gates, overlays, and reports. It
uses DexSim's SimReady API for asset facts and does not perform physical
parameter identification.

.. automodule:: embodichain.toolkits.dynamics_calibration
:members:
:undoc-members:
:show-inheritance:

Configuration
-------------

.. automodule:: embodichain.toolkits.dynamics_calibration.schema
:members:
:undoc-members:
:show-inheritance:

Metrics and qualification
-------------------------

.. automodule:: embodichain.toolkits.dynamics_calibration.metrics
:members:
:undoc-members:
:show-inheritance:

Candidate evaluation and tuning
-------------------------------

.. automodule:: embodichain.toolkits.dynamics_calibration.evaluator
:members:
:undoc-members:
:show-inheritance:

.. automodule:: embodichain.toolkits.dynamics_calibration.tuning
:members:
:undoc-members:
:show-inheritance:

.. automodule:: embodichain.toolkits.dynamics_calibration.tracking_evaluator
:members:
:undoc-members:

Artifacts and orchestration
---------------------------

.. automodule:: embodichain.toolkits.dynamics_calibration.overlay
:members:
:undoc-members:

.. automodule:: embodichain.toolkits.dynamics_calibration.asset_audit
:members:
:undoc-members:

.. automodule:: embodichain.toolkits.dynamics_calibration.report
:members:
:undoc-members:

.. automodule:: embodichain.toolkits.dynamics_calibration.cli
:members:
:undoc-members:
10 changes: 9 additions & 1 deletion docs/source/api_reference/embodichain/embodichain.toolkits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ embodichain.toolkits
====================

The :mod:`embodichain.toolkits` package contains asset-preparation and
manipulation utilities that can be used independently of the simulation loop.
manipulation utilities plus an isolated application-level dynamics-calibration
workflow.

.. automodule:: embodichain.toolkits

Expand All @@ -11,9 +12,16 @@ manipulation utilities that can be used independently of the simulation loop.
.. autosummary::

acd
dynamics_calibration
graspkit
urdf_assembly

.. toctree::
:maxdepth: 1
:hidden:

embodichain.toolkits.dynamics_calibration


GraspKit — Parallel-Gripper Grasp Sampling
-------------------------------------------
Expand Down
166 changes: 166 additions & 0 deletions docs/source/features/toolkits/dynamics_calibration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Dynamics Calibration

The dynamics-calibration toolkit tunes effective robot drive properties for a
specific EmbodiChain application. It produces a reviewable YAML overlay and
qualification evidence; it never rewrites the source URDF.

This V1 intentionally does **not** claim physical parameter identification.
Without torque/current or equivalent real-system evidence, mass, center of
mass, inertia, friction, stiffness, and damping are not uniquely identifiable
from position tracking alone.

## Workflow and ownership

The workflow has three commands:

1. `audit` delegates generic URDF and inertia checks to `dexsim.simready`.
2. `tune-drive` runs each candidate in a fresh process, ranks candidates on
training trajectories, then checks the winner on a held-out trajectory.
3. `qualify` rechecks an existing overlay against the current asset hashes and
held-out gates.

DexSim owns simulation-readiness facts. EmbodiChain owns application
trajectories, control-group selection, drive-parameter search, qualification
policy, and report assembly. An error-level DexSim finding blocks tuning;
warnings remain visible in the final report but permit application evaluation.

## Configuration

Save a YAML file such as `calibration.yaml`:

```yaml
schema_version: 1
assets:
- /absolute/path/to/robot.urdf
backend: physx
device: cpu
physics_dt: 0.004166666666666667 # 240 Hz
control_frequency_hz: 60
seed: 7
candidate_count: 9

evaluator:
target: embodichain.toolkits.dynamics_calibration.tracking_evaluator:evaluate
timeout_seconds: 120
payload:
control_part: arm
robot_cfg:
control_parts:
arm: [joint1, joint2, joint3]
training_trajectory:
duration_seconds: 3
warmup_seconds: 0.5
amplitude: [0.10, 0.08, 0.06]
frequencies_hz: [0.25, 0.35, 0.45]
qualification_trajectory:
duration_seconds: 4
warmup_seconds: 0.5
amplitude: [0.07, 0.11, 0.09]
frequencies_hz: [0.30, 0.40, 0.55]

parameters:
- name: arm_stiffness
field: stiffness
selector: arm
lower: 100
upper: 20000
initial: 5000
scale: log
- name: arm_damping
field: damping
selector: arm
lower: 10
upper: 2000
initial: 500
scale: log

qualification:
aggregate_rmse_max: 0.05
per_joint_rmse_max: 0.08
per_control_group_rmse_max: 0.06
cvar95_max: 0.12
# Custom step-response evaluators can also gate overshoot_max and
# settling_time_seconds_max when they return those metrics.
saturation_fraction_max: 0.02
velocity_saturation_fraction_max: 0.02
joint_limit_violation_max: 0
control_frequency_relative_error_max: 0
expected_target_qvel_write_count: 0
require_stable: true
```

Parameter `selector` values use the same exact-name, regular-expression, and
Robot control-part resolution as `RobotCfg.drive_pros`. The built-in evaluator
only writes qpos targets. Its qvel write count comes from public
`Articulation.set_qvel` instrumentation and counts successful batched target
write calls; it is not inferred from private engine state.

The requested control frequency must map to an integral number of physics
updates. Set `allow_approximate_control_frequency: true` only when a changed
actual rate is acceptable and covered by the configured frequency-error gate.
The default qpos-only policy also requires `target_qvel_write_count` evidence;
an evaluator that omits it fails that gate rather than being assumed to have
written zero velocity targets. Set `expected_target_qvel_write_count: null`
only when that instrumentation is intentionally unavailable.

## Commands

```bash
# Inspect one or more URDFs without changing them.
embodichain calibrate-dynamics audit robot.urdf --output-dir audit_output

# Audit, search, write the overlay, and run held-out qualification.
embodichain calibrate-dynamics tune-drive \
--config calibration.yaml \
--output-dir calibration_output

# Re-qualify a previously generated overlay.
embodichain calibrate-dynamics qualify \
--config calibration.yaml \
--overlay calibration_output/drive_overlay.yaml \
--output-dir qualification_output
```

An audit failure, worker exception, timeout, stale asset hash, or qualification
failure exits nonzero. Candidate results are content-addressed by the assets,
overlay, evaluator, runtime, backend, timestep, control schedule, seed, and
phase.

## Artifacts

`tune-drive` writes:

- `drive_overlay.yaml`: the selected `drive_pros` values and exact asset hashes;
- `report.json`: all candidates, raw metrics, hard-gate decisions, versions,
timing, device, backend, and cache provenance;
- `report.md`: a compact human review;
- `cache/`: reusable isolated-candidate results.

The report claim is always `effective_drive_tuning`. Confidence intervals and
domain-randomization ranges are marked as not estimated in V1; those require a
separate physical-identification workflow and suitable measurements.

## Custom application evaluator

Set `evaluator.target` to `module:function` or `/path/to/file.py:function`.
The callable receives `(overlay, context)` and returns a dictionary containing:

```python
{
"joint_names": ["joint1", "joint2"],
"target_qpos": [[0.0, 0.0], [0.1, -0.1]],
"actual_qpos": [[0.0, 0.0], [0.08, -0.12]],
"requested_control_hz": 60.0,
"actual_control_hz": 60.0,
"target_qvel_write_count": 0,
# Optional: control_groups, effort/effort_limits, qvel/qvel_limits,
# qpos_lower/qpos_upper, overshoot, settling_time_seconds, stable,
# and JSON-serializable metadata.
}
```

Tracking-error, saturation, limit metrics, and all hard gates remain centralized
in the toolkit. A custom step-response evaluator may additionally supply
application-defined `overshoot` and `settling_time_seconds` observations; the
toolkit validates them as finite non-negative values and applies the configured
gates without transforming them.
8 changes: 8 additions & 0 deletions docs/source/features/toolkits/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Available Toolkits
- Samples antipodal contacts, constructs grasp poses, and filters
collisions for parallel-jaw grippers.
- Annotate graspable regions and generate candidate grasps for manipulation.
* - :doc:`Dynamics Calibration <dynamics_calibration>`
- Audits robot assets, tunes effective drive parameters in isolated runs,
and applies hard gates on held-out trajectories.
- Produce a reviewable drive overlay for one application and backend.

Choosing a Toolkit
------------------
Expand All @@ -38,6 +42,9 @@ when the robot is distributed across multiple component files. Use grasp
generation after the target object's mesh is ready and a manipulation workflow
needs feasible end-effector poses.

Use dynamics calibration after a robot asset passes generic SimReady checks and
an application needs reproducible stiffness, damping, armature, or limit tuning.

The asset tools can be chained: preprocess component collision meshes, assemble
the components into one URDF, and then load the resulting robot in a grasping
task.
Expand All @@ -48,3 +55,4 @@ task.
URDF Convex Decomposition <convex_decomposition>
URDF Assembly <urdf_assembly>
Parallel-Gripper Grasp Generation <grasp_generator>
Dynamics Calibration <dynamics_calibration>
21 changes: 21 additions & 0 deletions docs/source/guides/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ The generated output contains the canonical source mesh under ``asset_source/``,

---

## Dynamics Calibration

Audit a robot description and tune effective drive properties for an
application without changing its source asset:

```bash
embodichain calibrate-dynamics audit robot.urdf
embodichain calibrate-dynamics tune-drive \
--config calibration.yaml \
--output-dir calibration_output
embodichain calibrate-dynamics qualify \
--config calibration.yaml \
--overlay calibration_output/drive_overlay.yaml
```

The tool uses DexSim for generic SimReady facts and EmbodiChain for trajectories,
search, and qualification. See {doc}`../features/toolkits/dynamics_calibration`
for the configuration schema, custom evaluator contract, artifacts, and scope.

---

(cli-preview-asset)=
## Preview Asset

Expand Down
5 changes: 5 additions & 0 deletions embodichain/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ class Command:
target="embodichain.toolkits.acd.cli:main",
help="Generate convex collision meshes for a URDF.",
),
Command(
name="calibrate-dynamics",
target="embodichain.toolkits.dynamics_calibration.cli:main",
help="Audit, tune, and qualify effective robot drive parameters.",
),
Command(
name="benchmark",
target="scripts.benchmark.__main__:main",
Expand Down
19 changes: 18 additions & 1 deletion embodichain/lab/sim/objects/articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ def __init__(
self.cfg = cfg
self._entities = entities
self.device = device
self._target_qvel_write_count = 0

# Store all indices for batch operations
self._all_indices = torch.arange(len(entities), dtype=torch.int32)
Expand Down Expand Up @@ -1504,6 +1505,19 @@ def get_qvel(self, target: bool = False) -> torch.Tensor:
"""
return self.body_data.qvel if not target else self.body_data.target_qvel

@property
def target_qvel_write_count(self) -> int:
"""Number of successful target-velocity API write calls on this handle.

The counter is application-facing instrumentation. One batched
:meth:`set_qvel` invocation counts once, independent of the selected
environments or backend implementation.

Returns:
Successful target-velocity calls since this handle was created.
"""
return getattr(self, "_target_qvel_write_count", 0)

def get_qvel_limits(
self,
joint_ids: Sequence[int] | torch.Tensor | None = None,
Expand Down Expand Up @@ -1536,7 +1550,8 @@ def set_qvel(
qvel (torch.Tensor): The velocities with shape (N, dof).
joint_ids (Sequence[int] | None, optional): Joint indices to apply the velocities. If None, applies to all joints.
env_ids (Sequence[int] | None, optional): Environment indices. Defaults to all indices.
If True, sets target positions for simulation. If False, updates current positions directly.
target (bool): If True, sets target velocities for simulation. If
False, updates current velocities directly.

Raises:
ValueError: If the length of `env_ids` does not match the length of `qvel`.
Expand Down Expand Up @@ -1591,6 +1606,8 @@ def set_qvel(
gpu_indices=indices,
data_type=data_type,
)
if target:
self._target_qvel_write_count = self.target_qvel_write_count + 1

def set_qf(
self,
Expand Down
Loading
Loading