Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.20.4] - 2026-05-04
Added
- Added research metric helpers and regression coverage for fluidity, smoothness, sign-flip, and metric-writer outputs.
- Added internal robustness verification tooling and elastic-band investigation notes.
Changed
- Improved
SCALE_ELASTICrecovery fluidity and position-step recovery semantics around constrained teleop stalls. - Avoided full collision scans on ordinary position-step success paths to reduce unnecessary collision-check overhead.
Fixed
- Fixed the public AI Worker teleop entrypoint so visual URDF resolution can require the public visual mesh while continuing to use bundled reduced collision geometry.
- Aligned AI Worker recovery tests with current solver intervention semantics.
[0.20.3] - 2026-04-29
Fixed
- Made copied/pip-installed AI Worker examples resolve bundled SG2 generated URDF assets before attempting network downloads.
- Added copied-example regression coverage so
embodik-examples --copyimports AI Worker helpers from the copied examples directory, not the source tree. - Updated AI Worker example docs and installer guidance around copied example usage.
[0.20.2] - 2026-04-28
Added
- Public ROBOTIS AI Worker constrained dual-arm teleop example (
examples/12_ai_worker_constraint_teleop.py) with cached public URDF resolution and reduced collision assets. - Shared
embodik.interactive_ikruntime helpers for robust interactive IK stepping, constrained last-safe restoration, and boundary stall classification. - AI Worker robustness harness and headless regression coverage for collision/CoM boundary behavior.
Changed
- Merged the simplified public example defaults from
mainand kept adaptive dt, nullspace bias, and balanced collision tuning as the default interactive behavior. - Simplified public-facing example/runtime code by moving detailed constrained-step handling out of the example layer.
- Updated Pixi test/build tasks to use
python -m pytestandpython -m pipso release workflow commands run reliably in the Pixi environment.
Fixed
- Hardened collision and CoM boundary handling around constrained teleop stalls, including target resync and safe-pose restoration.
- Preserved AI Worker visual meshes while using generated reduced collision geometry for IK collision checks.
- Cleaned up stale/order-dependent regression tests and documented removed expectations in internal notes.
[0.20.0] - 2026-04-19
Added
SolverStatus.COLLISION_VIOLATED(= 9): returned bysolve_position_stepwhen the input q was collision-safe but no integration step could maintainmin_distance.q_solutionis set to the last safe configuration. Hardware startup recovery (violated seed) never returns this status.- Per-link-pair collision distance overrides:
solver.set_collision_pair_min_distance(link_a, link_b, dist, activate_when_clear=True)sets a custom minimum clearance for specific link pairs. Uses deferred-latch activation by default — the override activates the first time the pair achieves the desired clearance, preventing immediate stalls when called from inside the threshold. PositionStepOptions.adaptive_dt: scales the integration timestep proportional to current position error for faster large-jump convergence without tuning gains. Configurable viaadaptive_dt_max_scaleandadaptive_dt_reference_distance. Includes proximity-aware cap to prevent overshoot stalls near collision boundaries.- Collision boundary tuning API:
set_collision_repulsion_deadband(),set_collision_recovery_scale(),set_collision_max_separation_speed_nonpenetrating()for runtime sweep/autoresearch without recompiling. evaluate_per_pair_override_violations(q): returns the worst margin across active per-pair override pairs; used internally in post-step rejection.- Example
11_collision_hardening_demo.py(Franka Panda interactive demo with collision status, per-pair sliders, nearest-point visualization). - Benchmarks:
benchmark_position_step_responsiveness.py,benchmark_boundary_oscillation.py. - Test suite:
test/test_collision_hardening.py(7 tests covering all new collision guarantees).
Changed
collision_repulsion_deadbanddefault changed from 3 mm to 0: the 3 mm no-braking zone created a 0.145 m/s velocity discontinuity that drove boundary-bounce oscillation. With 0, the lb formula provides a smooth deceleration ramp all the way tomin_distancewith no bounce.- Sparse position-limit constraint rows:
solve_velocitynow only adds QP rows for joints where position limits actually tighten beyond the velocity limit. Reduces constraint matrix size significantly for high-DOF floating-base robots (e.g. nv=47 → ~15 active rows at mid-range configuration). - Adaptive dt full-scan for large steps: when
adaptive_dtscales the integration step by > 1.01×, the post-step collision check uses a sphere-broadphase-expanded scan to catch pairs that were outside the active set before the jump. set_collision_pair_min_distancedefault changed toactivate_when_clear=True; useactivate_when_clear=Falsefor the previous immediate-activation behaviour.examples/02_collision_aware_IK.pyupdated with adaptive_dt controls (max_scale=10, ref_dist=0.02 defaults) andCOLLISION_VIOLATEDhold handling.
Fixed
- Critical: post-step rejection in both
solve_position_stepoverloads previously checkedkCollisionPenetrationDistanceThreshold(−1e-5) instead ofmin_distance. Solutions violating min_distance were silently returned asSUCCESS, requiring client-side collision checks. - Critical: multi-target
solve_position_steprejection was never updated from the original −1e-5 threshold (onlysolve_positionand single-target were fixed initially). - Critical: collision constraint permanently silenced in SPEED/BALANCED mode — the proximity-gated fast-path had no refresh-interval guard, causing
last_constraint_min_distance_to stay stale indefinitely. Fixed with refresh-interval check and delta-q guard (~0.1 rad motion invalidates stale cache immediately). - Critical: per-pair distance overrides not enforced in post-step rejection. Global
min_distancewas used, allowing custom pairs to penetrate undetected. Nowevaluate_per_pair_override_violations()is checked at all three rejection sites. - Desaturation nudge used
kCollisionPenetrationDistanceThresholdas safe_candidate threshold; could silently place robot insidemin_distancewithin the 0.5 mmnot_worsetolerance window. - Stall escape (Jacobian/normal) did not account for per-pair override thresholds.
- Removed
kCollisionViolationDeadband(1 mm dead zone with zero recovery force insidemin_distance); recovery ramp now activates immediately at the boundary.
[0.19.0] - 2026-04-04
Added
- Sphere broadphase collision culling: native AABB-derived bounding spheres skip expensive GJK/EPA distance queries for far-apart collision pairs. Achieves 30x collision speedup in isolation, 5790x combined with lazy reuse. New API:
solver.enable_sphere_broadphase(True),result.collision_sphere_culled_pairs. Enabled by default inkSpeedandkBalancedtuning modes. - Lazy constraint reuse: skip collision recomputation when joint velocity is near-zero and previous distance is safe. Eliminates 99%+ of collision steps in smooth trajectories.
- Post-step collision rejection: targeted safety check after integration prevents penetration without full-scan overhead.
- Elastic band joint limit expansion: temporary joint limit widening for overconstrained solver stalls. New
SCALE_ELASTICtask solve mode auto-enables elastic band. evaluate_min_collision_distance()Python binding: public API for querying collision distance at arbitrary configurations./releaseslash command: Claude Code slash command for version bump, changelog, and PyPI publish workflow.
Changed
- Default solve mode: examples 01-09 now default to
SCALE_ELASTICinstead ofSCALE. - Collision tuning presets:
kSpeedandkBalancedmodes now enable sphere broadphase automatically. - Example 02 defaults: self-collision and collision debug visualization enabled by default.
- Collision constraint reuse: coalesced nearest-points queries and added lazy constraint reuse — when joint velocity is near-zero and cached distance is safe, the entire collision constraint computation is skipped. Reuse is bounded by the cache refresh interval to guarantee periodic full scans.
- Position-step collision hot path: eliminated redundant full-scan collision overhead in position-step recovery paths; targeted pair evaluation replaces global distance scans during stall-escape and post-step rejection.
- Collision debug state preservation: lazy constraint reuse now preserves collision debug info (closest pair, nearest points) from the previous full computation, ensuring debug visualization remains available during reuse steps.
Removed
- Collision warm-start: removed unused
warm_start_collision_marginfeature from elastic band config (stall handler's reactive relaxation is preferred).
[0.18.9] - 2026-03-29
Added
- Focused solver perf gates: added targeted benchmark scripts for velocity and position-step collision workloads plus a hardened median-of-medians aggregator to make refactor acceptance decisions robust against run-to-run noise.
Changed
- Collision-heavy IK loop efficiency: reduced per-step overhead by removing avoidable allocations in stall/candidate handling, reserving hot-path containers, and simplifying collision debug evaluation in repeated checks.
- Position-step recovery hot path: skip redundant post-step collision rejection when integration does not move configuration, and cache collision-object-to-frame lookups during stall normal-escape attempts.
- Constraint bound readability: replaced unbounded torso-row magic literals with the shared
kUnboundedConstraintLimitconstant.
[0.18.8] - 2026-03-30
Added
- Position IK collision diagnostics counters:
PositionIKResultnow exposescollision_rejection_countandstall_escape_count(C++ + Python bindings) so callers can quantify rejected penetration steps and successful stall-escape nudges.
Changed
- Collision candidate-set safety fallback: collision row selection now forces a full scan when cached/top-K candidate subsets are underfilled or empty, and guarantees any currently penetrating pair is included in the active set before truncation.
- Collision debug semantics for top-K mode:
evaluate_collision_debug()now reports the globally closest pair rather than only the closest active constraint row, improving observability when the nearest pair is outside the current top-K active rows. - Penetration/stall constants normalized: newly introduced penetration-rejection and stall-escape thresholds in position-step paths are now defined as shared file-level constants instead of repeated inline literals.
- Stall-escape activation widened for clearance violations: stalled recovery now treats configured clearance violations (
distance < min_distance) as escape-eligible even when still non-penetrating, reducing prolonged zero-motion plateaus. - Debug instrumentation cleanup: removed session-specific C++ runtime log sinks used during incident debugging while preserving solver behavior changes validated by runtime evidence.
Fixed
- Position-step penetration regression guard:
solve_positionand bothsolve_position_stepoverloads now reject integration steps that newly create or significantly deepen collision penetration, reverting to the pre-step configuration when unsafe. - Stalled penetration escape robustness: when stalled in penetration, position-step IK now invalidates stale collision-pair caches and applies Jacobian-based escape nudges (active-row first, then global-pair fallback) to recover from deep contact states more reliably.
- Hard jump protection in step integration: sudden deep penetration transitions are now rejected via additional hard-jump thresholds in position-step rejection logic.
- Recovery gating when stall margin goes non-positive: collision rejection/escape paths now remain active when collision constraints are enabled, even if the temporary stall-adjusted
min_distancebecomes non-positive.
[0.18.7] - 2026-03-26
Changed
- Stall recovery bottleneck classification: stall handling now evaluates active collision-row distances from
get_last_collision_debug_list()(with backward-compatible fallback) instead of relying on a single scalar debug pair. - Position-step stall path parity:
solve_position/solve_position_stepnow propagate saturated-joint information into stall handling, matchingsolve_velocity-path bottleneck context.
Fixed
- Limit-dominated deep-collision pull-in: when joints are saturated near limits, stall recovery no longer relaxes collision margin as if collision were the primary bottleneck, avoiding false-positive margin ratcheting into penetration.
- Regression coverage for K>1 collision rows: added stall-handler tests for multi-constraint collision regimes (
max_constraints > 1) and parity checks for CoM-constrainedsolve_position_stepdirectional behavior.
[0.18.6] - 2026-03-26
Changed
- Stall-handler bottleneck gating tightened: stall recovery now relaxes collision
min_distanceonly when collision constraints are actually binding (distance <= current min_distance), preventing false-positive relaxation when joint-limit clamping is the real bottleneck. - Stall-handler docs clarified: API documentation now explicitly describes collision-only relaxation behavior and why joint-limit-driven stalls do not trigger collision-margin relaxation.
Fixed
- Deep-collision pull-in regression near joint limits: resolved a regression where repeated stall-threshold triggers could ratchet down collision margin while Jacobian clamping constrained motion, allowing tasks to pull the arm further into body collision.
- Regression test coverage for clamping-stall interaction: added/validated targeted tests ensuring stall recovery does not keep relaxing collision margins once joint limits become the bottleneck.
[0.18.5] - 2026-03-26
Changed
- Joint-limit handling simplification: removed the optional joint-limit barrier task API from C++/Python solver surfaces and internal solve path, keeping the near-limit Jacobian clamping strategy as the primary behavior near active limits.
- Examples/docs cleanup: removed barrier-specific controls and references from examples and recovery documentation to align with current default solver behavior.
Fixed
- Two-joint limit trap behavior: improved near-limit escape behavior by clamping task Jacobian entries that would push joints further into active limits, reducing apparent teleop stalls when one joint must move away while another remains near a bound.
- Regression stability around recovery fixtures: updated hardware-seed recovery checks to deterministic non-regression assertions so tests remain robust across environment-sensitive collision fixtures.
[0.18.4] - 2026-03-26
Added
- Explicit proximity-gating toggle API: added
set_proximity_gated_collision_activation_enabled(...)/get_proximity_gated_collision_activation_enabled()in C++ and Python so gating can be enabled/disabled without mutating threshold multipliers. - Regression coverage for gating toggle semantics: added tests verifying enable/disable behavior flips row activation while preserving configured activation multiplier and effective margin.
Changed
- Proximity-gated collision activation controls: added optional, min-distance-scaled activation threshold controls (
constraint_activation_multiplier, effectiveconstraint_activation_margin) and wired automatic margin updates whenmin_distancechanges. - Collision tuning preset behavior: restored
PRECISEto conservative legacy-equivalent behavior (cache disabled, no budget, gating off), whileBALANCEDandSPEEDenable proximity gating with distinct activation multipliers. - Collision row assembly path: collision QP rows are now emitted conditionally based on proximity threshold when gating is enabled, reducing far-from-collision overhead.
[0.18.3] - 2026-03-25
Changed
- Constraint assembly internals unified across APIs: extracted shared helper paths in the solver implementation for excluded-joint Jacobian masking, inequality-row appending, and torso pose-bound row construction so
solve_velocityandsolve_positionfollow one consistent constraint assembly pattern.
Fixed
- Reduced drift risk between
solve_velocityandsolve_position: replaced manual row-offset recomputation in position IK with the same append-cursor pattern used in velocity IK, reducing error-prone index math during future constraint additions. - Safety-net parity across solve APIs:
solve_positionnow runssanitize_solver_inputs(...)before backend solve, matchingsolve_velocityhandling for non-finite goal/Jacobian/constraint inputs. - Regression coverage for API consistency: added CoM-constraint parity coverage in
test/test_com_constraint.pyvalidating that both solve APIs improve or match unconstrained support-polygon violation behavior.
[0.18.2] - 2026-03-25
Fixed
solve_positionCoM constraint gap: position IK now includes configured CoM support-polygon inequality rows in its constraint stack (including excluded-joint handling), matching expected CoM-limit behavior already present in velocity IK.- Regression coverage for CoM-constrained position IK: added a targeted test in
test/test_com_constraint.pyto verify CoM-constraint configuration reduces or matches unconstrained support-polygon violation insolve_position.
[0.18.1] - 2026-03-23
Added
- Shared velocity-box headroom policy type: added
VelocityBoxHeadroomPolicy(enabled,fraction,activation_margin) and exposed it underTorsoPoseConstraintOptions.velocity_box_headroomin C++ and Python bindings. - Velocity-box helper API extension:
KinematicsSolver.calculate_velocity_box_constraint(...)now accepts optionalmin_velocity_headroomandheadroom_activation_marginarguments for explicit per-call headroom policy testing. PositionStepOptions.torso_constraint: optional torso orientation task and torso pose bounds on position-step IK; exposed in C++ and Python bindings (including.pyistubs).
Changed
- Torso pose-bound headroom path generalized: torso pose-bound rows now use the same shared
calculate_velocity_box_constraint(...)headroom path as other velocity-box constraints, replacing torso-specific post-processing. - Docs: Example 10 teleop docs now describe
velocity_box_headroomas the preferred API and markpose_bound_softening_*as legacy aliases.
Fixed
torso_constraintenforced insolve_position_step:PositionStepOptions.torso_constraintis now applied insolve_position_step(including the multi-target path) via inequality rows consistent withsolve_position, so teleop-style position stepping respects torso pose limits.- Bindings/stubs:
PositionStepOptionsin Python bindings and type stubs includestorso_constraintalongside other position-step fields. - Regression tests: expanded
test_position_step_joint_options.pycoverage for torso-constrained position stepping. - Validation coverage for headroom policy: regression tests for invalid
torso_constraint.velocity_box_headroom.fractionand preserved softening/backward-compat tests.
[0.18.0] - 2026-03-24
Added
TorsoPoseConstraintOptions.pose_bounds_reference_pose: optional 4x4 homogeneous anchor so torso pose box limits stay fixed relative to a chosen world pose across repeatedsolve_positioncalls (e.g. teleop ticks), while remaining fixed across inner IK iterations as before.- Torso pose-bound softening controls: added
TorsoPoseConstraintOptions.pose_bound_softening_enabledandpose_bound_softening_fraction(C++ + Python) as an opt-in way to preserve minimum torso-row velocity headroom near active pose-box limits. - Example 10 benchmark diagnostics:
scripts/benchmark_example10_torso_modes.pynow reports per-status timing/error buckets,iterations_useddistributions/histograms, and a jump-vs-no-jump plus max-iterations matrix sweep for deeper responsiveness analysis. scripts/install_embodik_macos.sh: one-shot macOS setup (Homebreweigen@3/ URDF packages, venv,SDKROOT+ SDK libc++ include workaround,CMAKE_PREFIX_PATHfor PyPIpin+ Homebrew, PyPI or editablepip install). Shipped in sdist viaMANIFEST.in.scripts/install_embodik_linux.sh: one-shot Linux setup (Debian/Ubuntuaptdeps for Eigen/URDF + toolchain, venv,CMAKE_PREFIX_PATHfrom PyPIpin, and PyPI or editablepip install). Shipped in sdist viaMANIFEST.in.
Changed
- Torso pose-box rotation error:
solve_positiontorso 6D bounds now usepinocchio::log3for the relative rotation (R_\mathrm{ref}^\top R) instead of a localacos/vee implementation, matching Pythonembodik.log3and improving numerical behavior near (\pi). - Example 10 interactive tuning workflow: added bounded-mode behavior presets (
Responsive,Stable,StrictBounds), torso bound softening controls, and aConstraint pressureindicator to make high-constraint stall risk visible during teleop-style target motion. - Docs (Pixi): Developer setup documents optional
pixi install, macOS Xcode CLT expectation, and that CMake adds the SDKlibc++include path on Apple platforms. - Docs / README: expanded pip/sdist guidance with one-shot installer coverage for both macOS and Linux, including optional
curlflows and distro-specific dependency notes. - CI (
test-macos-arm64): installeigen@3and URDF Homebrew packages; setSDKROOT, SDK libc++CXXFLAGS, andCMAKE_PREFIX_PATH="${PIN_PREFIX}:$(brew --prefix)". cibuildwheel(macOS):brew install eigen@3plus URDF packages;Eigen3_DIRunderopt/eigen@3;CMAKE_PREFIX_PATH=/opt/homebrewto complement CMake’s PyPIpinprefix prepended inCMakeLists.txt.
Fixed
- macOS Python module load after
pip install:INSTALL_RPATHfor_embodik_implnow uses separate Mach-O entries (@loader_path;@loader_path/lib) instead of one invalid$ORIGIN:$ORIGIN/libstring. - macOS builds with Xcode CLT:
CMakeLists.txtadds the active macOS SDK’susr/include/c++/v1onAPPLEsopixi run installand in-tree Ninja builds find<cmath>when CLT libc++ is incomplete; manual pip-only installs still use the documentedCXXFLAGSworkaround where needed.
[0.17.0] - 2026-03-23
Added
- Solver status/options API expansion for position stepping: introduced no-progress-oriented status and option surface in C++ and Python bindings so position-step callers can control and interpret bounded-step convergence behavior explicitly.
- Reference corridor support for position-step IK bounds: added reference-corridor-aware bound handling to improve guidance around prior solutions and reduce drift during repeated teleop-like updates.
- High-level collision tuning presets: added
CollisionTuningMode(PRECISE,BALANCED,SPEED) andKinematicsSolver.set_collision_tuning_mode(...)/get_collision_tuning_mode()so users can select collision behavior by intent instead of low-level cache/budget knobs. - Python enum exposure for collision tuning modes:
CollisionTuningModeis available in Python bindings and wired into solver bindings for direct use in scripts and applications.
Changed
- Unified IK bound evaluation path: consolidated position-step bound and stop-condition checks so joint/task limits, corridor checks, and termination semantics run through one consistent status-classification flow.
- Default no-progress behavior now classifies early exits explicitly: when incremental motion fails to make meaningful progress, the solver reports a no-progress outcome by default instead of collapsing into less specific statuses.
- Interactive example controls for live collision tuning: examples
02_collision_aware_IK.pyand03_teleop_ik.pynow expose aspeed/balanced/preciseselector with runtime updates while keeping low-level cache/budget knobs hidden. - Preset rationale documented in solver implementation: clarified why
PRECISEandBALANCEDdisable time-budget truncation (budget_us=0) and why onlySPEEDuses a positive refinement budget for bounded latency.
Fixed
- No-progress exit semantics consistency: aligned default no-progress classification across solver internals and bindings so callers observe stable, deterministic status reporting for stalled incremental updates.
[0.16.0] - 2026-03-21
Added
- Collision-query instrumentation in solver results:
VelocitySolverResultnow reportscollision_pairs_considered,collision_exact_distance_queries,collision_bound_culled_pairs, andcollision_budget_exhaustedfor per-step profiling. - Collision performance controls in public API: added
enable_collision_pair_cache(...),set_collision_refinement_time_budget_us(...), andget_collision_refinement_time_budget_us()onKinematicsSolverwith Python bindings. - Benchmark and equivalence tools: added
scripts/benchmark_teleop_workloads.pyandscripts/validate_collision_cache_equivalence.pyfor teleop-focused performance measurement and cache-vs-baseline correctness checks.
Changed
- Default collision path is optimized: collision pair caching and conservative budgeted refinement are now enabled with tuned defaults to reduce collision overhead in teleop-style loops while preserving conservative behavior.
- Dual-iiwa URDF utility flexibility:
build_dual_iiwa_urdf()now supports optional mesh-collision replacement viareplace_mesh_collision. - Local perf artifact hygiene:
.cursor/reports/perf_runs/*is now ignored to keep generated profiling outputs out of git tracking by default.
[0.15.6] - 2026-03-21
Added
- Panda joint-limit regression tests:
test_panda_position_limit_seed.py(at-limit seed vs HMND-style nudge undersolve_position_step) andtest_panda_joint_limit_barrier_position_step.py(barrier overhead / incremental teleop-like stepping). scripts/benchmark_joint_limit_barrier_overhead.py: optional local timing comparison for barrier ON vs OFF under repeatedsolve_position_stepcalls.
Changed
- Joint-limit barrier performance: cache the velocity→configuration index map across
solve_velocitycalls (rebuild when robot model ornvchanges); inject barrier objectives in one pass without allocating a fullnv-dimensional gradient vector. - Named barrier/limit constants:
KinematicsSolverusesstatic constexprvalues for barrier clamps, deadband math, numerical guards, and map sentinels instead of scattered literals. - Docs:
docs/joint_limit_saturation_exit_findings.mdtable updated to match the barrier implementation.
[0.15.5] - 2026-03-20
Changed
- Simplified stall recovery strategy: removed MIN_ERROR fallback toggling and iteration-cap coupling from the stall handler so recovery behavior is driven solely by collision-margin relaxation and restoration.
- Stall-handler API cleanup: reduced
configure_stall_handler()to core parameters (stall_threshold,restore_rate,floor_fraction) and removed fallback-specific state/config paths.
Fixed
- Collision margin consistency during recovery: unified restoration into a single ceiling-limited ramp that keeps effective
min_distancebelow live collision clearance, preventing abrupt re-entry into infeasible regions. - Regression coverage alignment with simplified behavior: updated stall-handler tests and docs/stubs to validate stable pull-away recovery and no-regression behavior without fallback-dependent assertions.
[0.15.4] - 2026-03-20
Added
pull-awaystall regression test: added dual-EE body-stall coverage that reproduces a stall-then-reverse-target sequence and asserts quick motion recovery when end-effector goals move away from collision.
Changed
- Stall detection now treats
kNumericalError+ near-zerodqas stuck: extends handler triggering beyondkInfeasibleso sustained capped-iteration failures still progress through recovery logic. - Iteration-cap probing under fallback: during fallback cycles, the solver now allows uncapped probe steps at cycle boundaries to detect newly feasible motion sooner when constraints are being relieved.
Fixed
- Collision margin clamp removed for runtime tuning:
set_collision_min_distance()now accepts negative values, enabling deep-penetration escape strategies that require temporary sub-zero margins. - Deep-penetration stall escape path: on repeated stalls while interpenetrating, the handler drops effective collision margin below current penetration and exits fallback so motion can resume instead of remaining locked with high compute cost.
- Margin restoration stability after escape: restoration is bounded by live collision-distance ceilings to avoid immediately reintroducing infeasible collision rows during recovery.
[0.15.3] - 2026-03-20
Changed
- Stall handler tuning is now parameterized: replaced inline magic constants in fallback logic with explicit config fields (
relax_drop_fraction,healthy_motion_multiplier,fallback_iteration_limit,task_active_weight_eps) to make behavior easier to reason about and tune. - Stall-handler defaults aligned with current behavior:
healthy_steps_to_cleardefault is now 10 inStallHandlerConfig, matching the sustained-motion requirement used by the active fallback implementation.
Fixed
- Removed ad-hoc floating-point guards in stall cleanup paths: stall margin restore/relaxed-state checks now use named tolerance constants rather than repeated hardcoded epsilons.
- Stall regression test robustness: threshold-reach assertion in dual-EE body-stall coverage now reflects observed counter accumulation under the updated sustained-motion fallback behavior.
[0.15.2] - 2026-03-20
Fixed
- Broadened stall detection under MIN_ERROR fallback: stall handler now detects stuck states when
dq ≈ 0under active MIN_ERROR fallback (returnskSuccesswith near-zero motion). Uses a wider velocity threshold (100×dq_stall_eps) when fallback is already active. - Progressive margin relaxation: fallback deactivation now requires collision margin to be fully restored to nominal before clearing, preventing rapid on/off cycling that re-triggered immediate stalls.
- Jump prevention on task disable: when all priority-0 tasks have near-zero weight (e.g., user disabling one EE during stall), the handler immediately restores collision margin to nominal and deactivates fallback, preventing configuration jumps into deep penetration.
- Computation time bounded during deep stalls: when the stall handler is in active fallback with sustained infeasibility, the inner SNS solver iteration limit is capped to 5, preventing per-step computation spikes from MIN_ERROR constraint-by-constraint saturation.
- Healthy-step gating: fallback deactivation requires sustained meaningful motion (above 10× the effective stall threshold), not just any non-zero
dq.
Added
TestDualEEBodyStalltest class: 5 new tests covering progressive margin relaxation, repeated threshold hits, computation time budget, no-penetration-jump on task disable, and velocity-loop stall reduction — all using the multi-targetsolve_position_steppattern matchinghmnd_robotteleop usage.
[0.15.1] - 2026-03-20
Changed
solve_position_step(..., stall_recovery=True)lifecycle: stall-handler state now persists across successive single-step calls so stall counters accumulate correctly in outer loops (e.g., teleop ticks withmax_steps=1).- Stall handler enabling semantics:
enable_stall_handler()is now idempotent and preserves accumulated recovery state when called repeatedly.
Fixed
- Persistent dual-EE stall handling: fixed a reset path that prevented
consecutive_stall_stepsfrom reaching threshold in per-tick stepping IK loops, which blocked stall recovery from activating. - Regression coverage: added/updated stall-handler tests for persistent
solve_position_stepbehavior and counter accumulation across repeated single-step calls.
Removed
- Solver recovery state machine: the internal velocity-solver recovery state machine has been removed entirely. It caused oscillation and aggressive velocity spikes without measurable benefit.
set_solver_recovery_enabled()/solver_recovery_enabled()are retained as no-ops for backward compatibility.
[0.15.0] - 2026-03-20
Added
- Stall recovery opt-in for
solve_velocity:KinematicsSolver::solve_velocity(..., stall_recovery=False)andsolve_velocity_dq(..., stall_recovery=False)now support one-flag activation of the C++ stall handler in user velocity loops. - Stall recovery opt-in for position IK:
PositionIKOptions.stall_recoveryenables the same C++ stall handling path forsolve_position(...)with sensible defaults. - Tests: expanded stall-handler coverage for
solve_velocity/solve_velocity_dqandsolve_positionflag behavior (default-off, opt-in behavior, and externally-enabled handler semantics).
Changed
solve_positionstall handling integration: stall detection now classifies low-level velocity outcomes consistently withsolve_velocity(including zero-scale primary-task infeasibility) before feeding the stall handler.
Fixed
- Stall-handler consistency across call paths: aligned status classification and option semantics so
stall_recoverybehaves consistently acrosssolve_velocity,solve_position_step, andsolve_position.
[0.14.4] - 2026-03-20
Fixed
solve_position_step(..., PositionStepOptions.excluded_joint_indices=...): restored consistent behavior with the existingsolve_positionintent by avoiding temporary mutation of registered task exclusion lists during stepping IK.- 0.14.3 regression note:
excluded_joint_indicesin stepping IK could cause undesirable behavior by patching registered task exclusions at runtime; this release removes that side effect.
Changed
- Stepping IK exclusions implementation:
excluded_joint_indicesnow uses a non-mutating per-step lock path (no scoped merge/restore of registered tasks).
Changed
clear_collision_constraint(): also resets active collision pair indices, stuck-detection counters, per-pair last-distance caches, and recovery homotopy state (collision_effective_min_distance_) so disabling collision does not leave stale internal state.- Recovery mode exit (collision health): when
max_constraints > 1, recovery now requires every active collision QP row (same entries asget_last_collision_debug_list()) to be at or abovemin_distance, not inference from the closest pair alone.
Removed
- Dead locals in
solve_velocityrecovery bookkeeping (collision_unhealthy/joint_unhealthy/unhealthy) that were never read.
Docs
- Header comments for internal recovery behavior (trigger,
goals[0]/ first merged priority-0 block, diagnostics viastatus_message) and expandedclear_collision_constraint()documentation. PositionStepOptions: clarify thatlocked_joint_indiceschanges the QP (not the same as post-QP masking); recommendintegration_zero_velocity_indicesalone for typical interactivesolve_position_steploops unless diagnostics require QP-consistent velocities.
[0.14.3] - 2026-03-19
Added
PositionStepOptions(aligned withPositionIKOptionsnaming where applicable):excluded_joint_indices: same meaning asPositionIKOptions::excluded_joint_indices— merged into exclusions on the driven pose task(s) and everyPostureTaskfor each innersolve_velocity, then restored (parity with howsolve_positionapplies exclusions to its frame + nullspace tasks).locked_joint_indices:nv-indices constrained to v = 0 in the velocity QP (tight bounds on the identity rows + Jacobian column zeroing on objectives and inequality constraints). Cleared automatically at the end of eachsolve_velocity.integration_zero_velocity_indices: after each innersolve_velocity, listednvcomponents are zeroed on the joint velocity beforepinocchio::integrate(legacy post-QP mask; QP may still have assigned non-zero velocity there).- Tests:
test/test_position_step_joint_options.py.
[0.14.2] - 2026-03-19
Added
- macOS Apple Silicon (Pixi):
osx-arm64inpixi.tomlworkspace platforms; lockfile regenerated for multi-platform solve. - CI:
macos-14job builds with venv + pip (pin+CMAKE_PREFIX_PATH) and runspytest. - Wheels workflow:
cibuildwheelonubuntu-latest(manylinux x86_64/aarch64) andmacos-14(arm64) withauditwheel/delocate-wheelrepair; sdist artifact; on version tags, publish combined artifacts to PyPI and create a GitHub release (generate_release_notes).
Changed
- Nanobind stubgen (CMake): use
DYLD_LIBRARY_PATHon Apple when running stub generation; includeDYLD_LIBRARY_PATHsegments in library search paths. embodik-sanitize-env: on macOS, sanitizeDYLD_LIBRARY_PATHinstead ofLD_LIBRARY_PATH.- Docs: README and
docs/installation.md— Apple Silicon notes,DYLD_LIBRARY_PATH/@rpathtroubleshooting, unset both loader vars in “existing Pinocchio” flow. - Release automation: tag-based PyPI upload moved to the wheels workflow;
release.ymlis manual (workflow_dispatch) for GitHub release only. scripts/upload_pypi.sh: comments updated for sdist-only script vs cibuildwheel artifacts.
Fixed
- CI / wheels:
find_package(Eigen3)failures — macOS job installs Eigen via Homebrew and setsEigen3_DIR;cibuildwheelLinux runsdnf install -y eigen3-devel; macOS wheel builds setEigen3_DIRfor Apple Silicon Homebrew. Docs updated for local macOS pip installs.
[0.14.1] - 2026-03-19
Added
PositionStepOptions: optional task-space speed capsmax_linear_speedandmax_angular_speed(≤0 = unlimited); applied insidesolve_position_stepafter gain × pose error.KinematicsSolver::clear_all_target_velocities()— clears directtarget_velocityon every registered task; exposed in Python bindings.- Tests:
test/test_position_step_speed_limits.py— stale-velocity cleanup after stepping IK and linear speed-cap behavior.
Changed
solve_position_step: after each call, clear direct velocities on all registered tasks (single-task path uses the same global cleanup as multi-task). Prevents stalesetTargetVelocity/ stepping leftovers from affecting the nextsolve_velocityor frame.
Docs
Task::getVelocity()— note that direct-velocity magnitude is not scaled byweight_; useweight = 0,active = false, orclearTargetVelocity()to drop contribution.
[0.14.0] - 2026-03-19
Added
- Stepping position IK —
KinematicsSolver::solve_position_step(...): interactive / per-frame position IK that does not swap or create temporary tasks. It sets the target on a registered pose task, computes pose error internally, maps it to a task-space velocity using separate linear/angular gains (PositionStepOptions, independent of task weight), runssolve_velocity()up tomax_stepswith optional per-step integration (dt≤ 0 usessolver.dt). Because it goes throughsolve_velocity(), the same collision and limit machinery applies each sub-step. - Multi-task stepping IK — overload
solve_position_step(current_q, std::vector<TaskTarget>, options)for one coordinated velocity solve per sub-step across multiple targets. Supports registeredFrameTask,AbsoluteFrameTask, andRelativeFrameTask(by task name); eachTaskTargetcarries its own 4×4 pose and gains. PositionStepOptions:position_gain,orientation_gain,max_steps,dt.TaskTarget:task_name,target_pose,position_gain,orientation_gain.- Python bindings:
TaskTargetandPositionStepOptions;TaskTarget.from_se3(...);solve_position_stepoverload acceptingembodik.Rt/ Pinocchio SE3 for the single-task path (in addition to 4×4ndarray). FrameTask::getFrameName()— public accessor for the controlled frame id.- Solver recovery state machine (velocity / stepping paths): ring buffer of recent solve snapshots; stuck trigger on sustained
NUMERICAL_ERRORwith near-zero||dq||; rollback toward the best recent feasible configuration; recovery mode that softens primary EE-style objectives and biases toward collision clearance and limit margin; per-pair collision margin homotopy (collision_effective_min_distance_) to ease out of persistent margin violations; healthy exit counters before returning to normal. - Examples:
examples/utils/pose_utils.py(PoseUtils.make_pose_matrix); Viser examples 01, 02, 03, 08, 09 updated to usesolve_position_step, aligned default sliders (task weight / gains / iterations), and shared EE solve mode + allow SCALE→MIN_ERROR fallback controls where applicable. - Tests:
pytest-benchmarkforsolve_position_stepvs a low-level reference loop; stepping API +Rt/SE3 overload coverage (test_adaptive_task_relaxation.py); defaultallow_min_error_fallbackround-trip (test_tasks.py); collision recovery / cold-start / deadlock rollouts (test_embodik.py).
Changed
- Default task behavior:
Task::allow_min_error_fallbacknow defaults tofalse. Code that depended on automatic SCALE → MIN_ERROR fallback without setting the flag must opt in withallow_min_error_fallback = True(matches interactive examples and coordinated IK expectations). solve_position_stepperformance: O(1) task lookup viatask_map_, singlesetTargetPoseinstead of split setters (one cache invalidate), fixed-size 6D velocity buffer in hot loops,std::movemerge ofVelocitySolverResultintoPositionIKResult, multi-task path resolves dynamic task types once per call (not every sub-step).- Examples / scripts: reuse a single
PositionStepOptionsinstance in tight loops where practical;visualization_example.pyusesPoseUtils.
Fixed
- Clearer invalid-input messages for stepping IK (unknown task name, non-
FrameTaskname in single-task API, unsupported task type in multi-task list).
Docs
docs/api/tasks.md— document defaultallow_min_error_fallbackfor user-created tasks.
Note (test suite)
- Full
pixi run teston this date: 247 passed, 7 skipped, 3 failed (known debt, not introduced by the stepping-IK API itself): test_joint6_drift_bug:test_x_positive_drive_causes_j6_drift,test_y_positive_drive_causes_j6_drift— joint-6 drift exceeds the current tight threshold for pure ±X/±Y EE drives.test_panda_narrowed_limits:TestStrategyComparison.test_strategy_a_vs_baseline[X]— strategy A reverse-path stall count vs baseline.
[0.13.2] - 2026-03-12
Added
- Collision-boundary jitter guardrails for adaptive relaxation: new regression tests compare
SCALEvsMIN_ERRORnear active collision-distance limits using both distance-stddev and sign-flip metrics.
Changed
- Near-boundary collision handling now applies a small violation dead-zone just below
min_distanceto reduce numerical chatter while preserving recovery behavior for real violations.
[0.13.1] - 2026-03-12
Added
- Regression coverage for adaptive task relaxation on Panda, including
MIN_ERRORprogress checks with and without nullspace posture bias. - Adaptive relaxation is now enabled by default for
SCALEtasks via automatic fallback toMIN_ERRORwhen scale collapses.
Changed
- Refined clamped
MIN_ERRORactive-set behavior to preserve hierarchical projector updates and improve constrained progress when saturation evolves. - Kept the release focused by dropping temporary scripted trace/debug tooling from examples.
Fixed
- Resolved a
MIN_ERRORloop-control path that could skip objective projector updates and degrade lower-priority task behavior. - Resolved
MIN_ERRORtermination fallback handling to avoid pathological stagnation/oscillation in constrained scenarios.
[0.13.0] - 2026-03-10
Added
- New public solver outcome
INFEASIBLEacross C++/Python status enums and stubs to distinguish clean infeasibility from numerical failures. - Explicit high-level status-message coverage for infeasible outcomes in velocity and position solving, including position-IK non-convergence classification.
- Regression tests for status exposure, infeasibility hinting, and high-level infeasible/non-convergence behavior.
Changed
- High-level
KinematicsSolvernow classifies numerically stable but unachievable outcomes asINFEASIBLEinstead of overloadingNUMERICAL_ERROR. solve_velocity()now preserves and surfaces backendstatus_messagedetails to downstream callers.- CoM rollout tests now accept
INFEASIBLEas a valid constrained-solve outcome while preserving behavioral assertions.
[0.12.6] - 2026-03-10
Added
- Guided solver debugging helper:
embodik.get_solver_status_hint(status, status_message=None)returns actionable troubleshooting guidance per solver status and appends backend details when available. SolverResult.status_messageis now exposed to Python bindings for explicit failure diagnostics.- Explicit solver status coverage in Python: new enum values exported (
SHAPE_MISMATCH,EMPTY_PROBLEM,CONSTRAINT_BOUNDS_MISMATCH,NON_FINITE_INPUT) for clearer handling in downstream code. - New regression test for status hint helper and updated invalid-input tests to validate explicit status categories.
Changed
- Constraint robustness logic is streamlined via shared internal helpers for half-space bound shaping and violated-row task projection, and applied consistently across collision, CoM, and relative-pose constraints.
- Solver pre-processing now sanitizes non-finite values and inconsistent per-row bounds before backend solve, reducing NaN-driven ambiguous failures.
- Input-validation failures in the backend now return explicit status categories with descriptive messages instead of lumping all failures into generic
INVALID_INPUT.
[0.12.5] - 2026-03-10
Changed
- CoM support-polygon constraints now apply collision-style violation handling: when outside a half-plane, task Jacobians are projected to remove outward motion components while preserving tangential/inward directions.
- CoM constraint bounds now include active outside recovery behavior with minimum inward recovery speed and proportional scaling, improving escape behavior when starting in violation.
- CoM half-plane construction now includes a centroid-side orientation guard so feasibility is robust to polygon winding/orientation.
Added
- New CoM regression tests covering outside-violation behavior:
- single-step suppression of further outward motion while outside, and
- multi-step constrained-vs-unconstrained rollout comparison.
[0.12.4] - 2026-03-06
Changed
- Saturation exit behavior disabled by default: Velocity-box softening near joint limits (kMinBoundFraction) is now opt-in. Use
enable_saturation_exit_behavior(True)to restore previous behavior; requires more testing before enabling broadly.
Added
enable_saturation_exit_behavior(bool)/saturation_exit_behavior_enabled(): Python API to toggle velocity-box softening near limits.
[0.12.3] - 2026-03-06
Added
- Native Rotation helper (
embodik.Rotation/SO3): Lightweight SO(3) class replacingscipy.spatial.transform.Rotation. Constructors:from_matrix,from_quat,from_rotvec,from_euler,identity. Spatialmath-style shorthands:Rx,Ry,Rz,RPY,AngVec,EulerVec. - SE3 property aliases:
R,t,A(spatialmath-style).SE3.Rt(R, t)classmethod. - Transform benchmarks (
pixi run benchmark-transforms): Latency and throughput benchmarks; native r2q/q2r ~24×/2× faster than SciPy.
Changed
- r2q / q2r: Now use native Pinocchio conversion (no SciPy dependency).
- API documentation: Added
docs/api/transforms.mdfor Rotation, SO3, SE3. Updateddocs/api/utils.mdwith r2q, q2r, Rt, compute_pose_error.
[0.12.2] - 2026-03-06
Added
- SE3 Python API: Composition operator (
T1 * T2), equality (==,!=), and point transforms (act(p),actInv(p)). Enablesgoal_pose = torso_emb * target_se3andp_world = T.act(p_local)without manual matrix math.
[0.12.1] - 2026-03-06
Fixed
- Decoupled velocity-solver tolerances to avoid unintended over-regularization:
set_tolerance()now maps to regularized pseudoinverse damping threshold (regularization_config.epsilon), preserving historical behavior.- Added
set_constraint_tolerance()to control constraint violation deadband (VelocitySolverConfig::epsilon) independently. - Updated Python bindings/docstrings to reflect the separated semantics.
[0.12.0] - 2026-03-05
Added
- Top-K simultaneous collision constraints (
max_constraintsparameter onconfigure_collision_constraint()): instead of protecting only the single globally closest pair, the solver now emits up to K independent QP constraint rows — one per closest pair. Each row has its own Jacobian, velocity-damper bounds, and stuck-detection counter, so multiple tight-clearance regions (e.g. base/leg and arm/torso on a wheeled humanoid) are protected simultaneously. get_last_collision_debug_list(): returns alist[CollisionDebugInfo]with one entry per active constraint row (up tomax_constraints). The existing single-pairget_last_collision_debug()remains unchanged for backward compatibility.- Per-pair hysteresis and stuck detection: previous active pair indices are tracked as a vector; each pair has its own stuck counter in a map. Pairs that fall out of the top-K are cleaned up automatically each step.
Changed
- Continuous recovery ramp (collision escape improvement): removed the discrete "slightly-inside deadband" special case that used a
gentle_scale = 0.01multiplier (producing ~0.005 m/s — too weak to overcome EE task pulls). All non-penetrating violations now use a uniformkCollisionRecoveryScale = 0.2, producing a proportional recovery push at all violation depths. - Task normal projection (collision escape improvement): when a collision pair is violated (
lower_bound > 0), the collision normal is projected out of all EE task Jacobians before the SNS solve. Only the approach component (proj < 0) is removed; tangential and escape directions remain fully available. This eliminates the "frozen in all directions" symptom where the entire task velocity was scaled to zero by the SNS solver. kCollisionStuckRecoverySpeed = 0.10 m/sadded: stuck condition now boosts recovery to at least 0.10 m/s (previously 0.05 m/s, shared with penetration floor).
[0.11.0] - 2026-03-04
Added
- RobotModel constructor with actuated joint names:
RobotModel(urdf_path, actuated_joint_names=[...], floating_base=False)builds a reduced model by locking all joints not in the list at their neutral configuration. The resulting model'snq/nvmatch the actuated joint count, eliminating index mapping when integrating with external systems (e.g. hmndlib) that use reduced configurations. Visual and collision geometry are reduced in sync with the model.
Changed
- For floating-base robots, the root freeflyer joint is never locked when using the actuated_joint_names constructor.
[0.10.0] - 2026-03-03
Added
- ECTS (Extended Cooperative Task Space): Dual-arm coordination tasks (reference: H. A. Park, IROS 2016, doi:10.1109/IROS.2016.7759161).
add_absolute_frame_task(): Alpha-blended midpoint frame (Serial L/R, Parallel, Blended).add_relative_frame_task(): Grasp configuration (right EE relative to left EE).add_frame_task(): Single-frame pose task for Orthogonal mode.embodik.map_ects_mode(),map_ects_mode_blended(): ECTS config mapping.- Relative pose constraint:
configure_relative_pose_constraint(),clear_relative_pose_constraint()for bounds on stored relative target. - Interactive example (
09_dual_arm_ects.py): Dual LBR iiwa ECTS demo with Viser. - Six coordination modes (Orthogonal, Serial L/R, Blended, Parallel).
- Orthogonal mode: two independent EE pose controls (blue marker → left EE, green → right EE).
- Mode-change snap keeps arms at current config; markers snap to effective frames.
- Collision avoidance with primitive collision shapes; collision debug visualization.
- Dual iiwa URDF builder (
utils/dual_iiwa_urdf.py): Composes two iiwa14 arms with shared base. - Strips Drake namespace attributes (
drake:acceleration) for parser compatibility. - Replaces high-poly mesh collision (links 6 & 7) with spheres for ~100× faster collision queries.
Changed
- Renamed
ects.hpp/ects.cpptodual_arm_ects.hpp/dual_arm_ects.cppfor clarity. Added IROS 2016 paper citation in headers.
Fixed
- Drake iiwa mesh-based collision geometry (links 6 & 7) caused ~100× slowdown; now replaced with sphere primitives in dual_iiwa_urdf.
[0.9.0] - 2026-03-03
Added
- Joint-limit barrier gradient task:
set_joint_limit_barrier_task(margin, gain)andclear_joint_limit_barrier_task()to proactively drive joints away from limits via an analytical barrier gradient in nullspace. Reduces EE return error and stalls when joints saturate during round-trip motion. - Pose metrics module (
embodik.pose_metrics): C++ implementations with Python bindings forjoint_limit_distance,joint_limit_distance_gradient,velocity_manipulability, andsingularity_joint_limit_metricfor diagnostics and monitoring. - Interactive example (
01_basic_ik_simple.py): Joint limit scaling slider, barrier task toggle, and real-time pose metrics display.
Fixed
- Joint-6 spurious drift bug:
kMinBoundFractionin velocity box constraints no longer injects headroom toward a limit when a joint is already at that limit. AddedkMarginThreshold(0.01 rad) so softening applies only in the direction away from limits. - Limit scaling in example: When narrowing joint limits via the slider, the current configuration is now clipped to the new limits to avoid spurious recovery behavior.
Changed
- Moved
kMinBoundFraction,kMarginThreshold, and pose_metrics constants to file-level for clarity.
[0.8.1] - 2026-02-27
Changed
- CoM margin definition: The fractional margin for polygon shrink now uses
the mean distance from centroid to vertices (
char_size) instead of the minimum distance (min_radius), producing more consistent shrink behavior across polygon shapes.
[0.8.0] - 2026-02-27
Added
- CoM support-polygon inequality constraint API: Enforce the 2D projection of the center of mass to remain inside a convex polygon via per-half-plane QP constraints.
KinematicsSolver.configure_com_constraint(): Configure constraint with polygon vertices, fractional margin shrink, velocity/acceleration limits, and proximity-based activation.KinematicsSolver.clear_com_constraint(): Disable CoM constraint.KinematicsSolver.get_com_proximity_threshold(): Read back the auto-computed activation distance (proximity_fraction × polygon inradius).- Convex hull computation, half-plane extraction, and polygon inradius are handled internally in C++ — callers only provide raw vertices.
- Three-layer velocity bound per half-plane (matching Spot Flex IK pattern):
com_vel_max— always active, caps CoM speed in every direction.sqrt(2 * com_acc_max * slack)— always active, smoothly tapers approach speed well before the boundary (bounded tipping energy).slack / dt— active only near the boundary (when slack < proximity threshold), prevents overshooting in a single time step.- Anti-chattering at boundary: Slack clamped to non-negative for position
and acceleration terms; epsilon dead-zone (0.1 mm) prevents sign-flip
oscillation from numerical noise. Matches the
kMarginEpsilonpattern used in joint position-limit constraints. - Interactive Viser example (
examples/08_com_constraint_example.py): CoM sphere, floor projection disk, vertical drop line, inner/outer polygon boundaries, and color-coded status (green/orange/red). GUI sliders for all constraint parameters. - Unit tests (
test/test_com_constraint.py): API usage, invalid input rejection, Jacobian shape, CoM containment, velocity/acceleration limits, and frame transforms (123 total tests pass).
[0.7.2] - 2026-02-09
Fixed
- Improved position-limit robustness near joint bounds: Increased the
position-limit margin used for position-based velocity constraints from
1e-4to1e-3radians to better absorb solver tolerance and numerical drift during integration. - Post-solve velocity bound enforcement for limited solves:
KinematicsSolver.solve_velocity(..., apply_limits=True)now clamps the returned velocity vector to the effective per-joint bounds (including the intersection of velocity and position-based bounds when both are active).
[0.7.1] - 2026-02-09
Fixed
- Joint position limit enforcement with mixed joint representations:
KinematicsSolver.solve_velocity(..., apply_limits=True)now maps velocity-space indices (v) to the correct configuration-space indices (q) when building position-based velocity constraints. This fixes cases where models include continuous joints (nq=2, nv=1) ahead of bounded revolute joints and the bounded joint could be constrained using the wrongqindex. - Added regression coverage in
test_joint_limit_recovery.pyfor a chain containing a continuous joint followed by a bounded revolute joint to ensure integrated solutions remain within revolute position limits.
[0.7.0] - 2026-02-06
Added
- Joint index access API on
RobotModel: Expose per-joint configuration-space and velocity-space indexing, matching Pinocchio'smodel.idx_qs/nqs/idx_vs/nvsarrays through a clean name-based Python API. RobotModel.has_joint(joint_name): Check if a joint existsRobotModel.get_joint_id(joint_name): Get the internal joint indexRobotModel.get_joint_config_index(joint_name): Starting index in q (idx_q)RobotModel.get_joint_config_size(joint_name): Number of config variables (nq)RobotModel.get_joint_velocity_index(joint_name): Starting index in v (idx_v)RobotModel.get_joint_velocity_size(joint_name): Number of velocity variables (nv)
Notes
These APIs let users build per-joint q/v mappings without importing Pinocchio directly, enabling cleaner joint-to-motor mapping code. For revolute joints, nq=nv=1. For continuous joints, nq=2 (cos/sin) and nv=1. For floating-base, nq=7 (xyz + quaternion) and nv=6 (linear + angular velocity).
Migration Guide
Replace direct Pinocchio joint index access:
# Old - required pip pinocchio
import pinocchio as pin
model = pin.buildModelFromUrdf("robot.urdf")
joint_id = model.getJointId("joint1")
idx_q = model.idx_qs[joint_id]
nq = model.nqs[joint_id]
idx_v = model.idx_vs[joint_id]
nv = model.nvs[joint_id]
# New (v0.7.0) - no pip pinocchio needed
import embodik
robot = embodik.RobotModel("robot.urdf")
idx_q = robot.get_joint_config_index("joint1")
nq = robot.get_joint_config_size("joint1")
idx_v = robot.get_joint_velocity_index("joint1")
nv = robot.get_joint_velocity_size("joint1")
[0.6.0] - 2026-02-06
Added
- Inverse dynamics API on
RobotModel: Full dynamics computations via Pinocchio's RNEA and CRBA algorithms, exposed through native C++ bindings. RobotModel.compute_generalized_gravity(q): Compute gravity torque vector g(q)RobotModel.rnea(q, v, a): Full inverse dynamics via Recursive Newton-Euler Algorithm (returns tau = M(q)a + C(q,v)v + g(q))RobotModel.compute_mass_matrix(q): Joint-space mass/inertia matrix M(q) via CRBARobotModel.compute_coriolis(q, v): Coriolis + centrifugal torque vector C(q,v)*vRobotModel.set_gravity(gravity)/get_gravity(): Configure gravity vector- Boolean collision checking on
RobotModel: RobotModel.check_collision(min_distance=0.0): Returns True if any collision pair has distance below threshold. Simpler API thancompute_min_collision_distance()for pass/fail collision queries.
Notes
These APIs unify robot model functionality that was previously scattered across
separate Pinocchio imports (e.g. pin.computeGeneralizedGravity, pin.rnea,
downstream packages collision checks) into EmbodiK's single RobotModel class,
eliminating the need for a separate pin runtime dependency for dynamics.
Migration Guide
Replace direct Pinocchio dynamics calls:
# Old - required pip pinocchio
import pinocchio as pin
model = pin.buildModelFromUrdf("robot.urdf")
data = model.createData()
tau_gravity = pin.computeGeneralizedGravity(model, data, q)
tau_total = pin.rnea(model, data, q, v, a)
# New (v0.6.0) - no pip pinocchio needed
import embodik
robot = embodik.RobotModel("robot.urdf")
tau_gravity = robot.compute_generalized_gravity(q)
tau_total = robot.rnea(q, v, a)
Replace separate collision checking libraries:
# Old - required direct pinocchio
robot_state.check_collision()
# New (v0.6.0) - built into EmbodiK
robot.update_configuration(q)
in_collision = robot.check_collision() # contact check
too_close = robot.check_collision(min_distance=0.02) # proximity check
[0.5.0] - 2026-02-06
Added
- Lie-group-aware
integrate()method onRobotModel: Properly integrates joint velocities into configurations using Pinocchio's manifold-aware integration. For standard revolute/prismatic joints this is equivalent toq + v*dt, but for floating-base (SE3), spherical (quaternion), and other non-Euclidean joint types it performs the correct exponential-map integration (e.g. quaternion update viaexp). This replaces naiveq += dt * dqaddition which breaks quaternion unit-norm constraints and gives wrong results for continuous and floating-base joints. RobotModel.integrate(q, v, dt=1.0): Manifold integrationRobotModel.difference(q0, q1): Inverse of integrate; returns tangent vectorRobotModel.neutral_configuration(): Returns the home/zero configuration (valid quaternion for floating-base)RobotModel.random_configuration(): Random valid configuration within joint limitsRobotModel.normalize(q): Re-normalize quaternion components of configuration
Fixed
- Position IK
solve_position()now usespinocchio::integrate()instead of naiveq += dt * dqfor velocity integration. This fixes incorrect behavior for floating-base robots where quaternion components were being updated via simple addition, breaking the unit-norm constraint and producing invalid configurations.
Migration Guide
Users who were manually integrating velocities from solve_velocity() using q += dt * dq
should switch to RobotModel.integrate():
# Old (v0.4.x) - broken for floating-base / quaternion joints
result = solver.solve_velocity(q)
q = q + dt * np.array(result.solution) # WRONG for floating-base!
# New (v0.5.0) - correct for all joint types
result = solver.solve_velocity(q)
q = robot.integrate(q, np.array(result.solution), dt) # Always correct
For computing configuration differences:
# Old - broken for floating-base
delta = q1 - q0 # WRONG for floating-base
# New - correct for all joint types
delta = robot.difference(q0, q1) # Returns tangent vector (size nv)
[0.4.0] - 2026-02-05
Breaking Changes
- Removed Python
pinpackage from runtime dependencies: EmbodiK now uses native C++ bindings exclusively for all Pinocchio functionality. Thepip pinocchio(pin) package is no longer required at runtime, only at build time. - This resolves numpy dependency conflicts when using EmbodiK with other packages that have different numpy version requirements
- All rotation utilities (log3, exp3, quaternion conversions) now use native bindings
- Collision distance computation now uses native
RobotModel.compute_min_collision_distance() - Visualization defaults to direct Viser (no Pinocchio ViserVisualizer dependency)
Added
- Native math utilities via nanobind: New C++ bindings for rotation/pose math
embodik.log3(R): Compute axis-angle from rotation matrix (replacespin.log3)embodik.exp3(omega): Compute rotation matrix from axis-angle (replacespin.exp3)embodik.matrix_to_quaternion_wxyz(R): Convert rotation matrix to (w,x,y,z) quaternionembodik.quaternion_wxyz_to_matrix(w,x,y,z): Convert quaternion to rotation matrix- Native collision distance API: RobotModel now exposes collision distance methods
RobotModel.compute_min_collision_distance(): Get minimum distance across all collision pairsRobotModel.compute_collision_distances(): Get distances for all collision pairs- Optional Pinocchio visualization:
pip install embodik[visualization-pinocchio]for Pinocchio's ViserVisualizer (requirespin>=3.8.0)
Changed
- Visualization now defaults to direct Viser implementation (no
pinneeded) utils.pyfunctions (get_pose_error_vector,compute_pose_error,Rt) use native bindingsvisualization.pyquaternion functions use native bindings- GPU collision fallback uses native
RobotModel.compute_min_collision_distance() - Removed
_runtime_deps.py(lazy Pinocchio import no longer needed)
Migration Guide
If you were using Pinocchio Python bindings directly through EmbodiK:
# Old (v0.3.x) - required pip pinocchio
import pinocchio as pin
R_error = pose_goal.rotation @ pose_current.rotation.T
error = pin.log3(R_error)
q = pin.Quaternion(R)
# New (v0.4.0) - no pip pinocchio needed
import embodik as eik
R_error = pose_goal.rotation @ pose_current.rotation.T
error = eik.log3(R_error)
w, x, y, z = eik.matrix_to_quaternion_wxyz(R)
For collision distance computation:
# Old (v0.3.x) - required pip pinocchio
import pinocchio as pin
pin.updateGeometryPlacements(model, data, collision_model, collision_data)
for i in range(len(collision_model.collisionPairs)):
pin.computeDistance(collision_model, collision_data, i)
dist = collision_data.distanceResults[i].min_distance
# New (v0.4.0) - native API
robot_model.update_configuration(q)
dist = robot_model.compute_min_collision_distance()
[0.3.0] - 2026-02-03
Added
- PPH-SNS Solver: Alternative GPU-optimized solver (Parallel Penalized Hierarchical SNS)
- Soft top-k violation selection using softmax weights
- Limited rank-1 projector updates (1–2 violators per iteration)
- Achieves ~632,000 IK solves/second at batch size 10,000
- Benchmark comparison scripts:
benchmark-solver-comparison,benchmark-solver-batched - GPU Acceleration: Batched velocity IK solving with massive parallelism (100-500x speedup)
- Achieves ~670,000 IK solves/second at batch size 10,000
- Ideal for RL training (4096+ parallel environments), motion planning, and dataset generation
- GPU batched solver via CusADi-compiled CUDA kernels
- FI-PeSNS Solver: Fixed-Iteration Penalized eSNS algorithm optimized for GPU
- Singularity-Robust Inverse (SRINV) for numerical stability
- Analytical scaling for feasible task scales without iterative saturation
- Penalty gradient approach for constraint enforcement
- Fixed iterations for predictable compute time (ideal for real-time RL)
- GPU Collision Detection: GPU-accelerated collision detection via NVIDIA Warp
- Batched collision queries for parallel processing
- Experimental support for GPU-accelerated self-collision avoidance
- Parallel Trajectory Tracking Demo: Visualize 100 robot instances simultaneously tracking different trajectories
- Interactive demo with Viser visualization
- Demonstrates GPU parallelization capabilities
- Achieves ~50,000+ IK solves/second with GPU acceleration
- Teleoperation IK Example: Interactive IK control using Seer wireless controller (Xvisio SDK)
- GPU Benchmarks and Demos: Comprehensive benchmarking tools for GPU performance
- Batch IK performance benchmarks
- FI-PeSNS vs CPU accuracy benchmarks
- Collision detection benchmarks
- GPU solver demonstration panels
- CasADi Integration: Export and compile velocity IK functions to CUDA kernels
- Symbolic function export for GPU compilation
- CusADi integration for CUDA kernel generation
- GPU Environment Support: CUDA feature in pixi.toml with GPU-specific tasks
check-cuda,check-gpu,install-cusaditasks- GPU demos and benchmarks via pixi tasks
- CUDA environment configuration
Changed
- Enhanced examples with GPU acceleration support
- Updated dependencies to include xvisio SDK support for teleoperation examples
- Improved GPU documentation and setup instructions
Dependencies
- Added
casadi>=3.6.0andtorch>=2.0.0to[gpu]optional dependencies - Added
warp-lang>=1.0.0to[gpu-collision]optional dependencies - Added
xvisio>=0.3.1to pixi dependencies for teleoperation examples
[0.2.0] - 2026-01-30
Added
- Position IK now supports
excluded_joint_indicesto lock joints during solves - Position IK can enforce collision constraints during iterative solves (when configured)
Changed
- Velocity solver now groups same-priority tasks to make ordering within a priority level symmetric
- Collision constraint computation now uses cached allow-masks to skip excluded pairs efficiently
- Collision recovery near
min_distanceuses deadband + adaptive push to reduce jitter and stalling - Collision constraint default
nearest_points_all_pairsset to true - Collision debug evaluation is side-effect free (no solver state mutation)
Fixed
- Position IK now applies collision constraints and excluded joint indices consistently with velocity IK
- Collision constraint pair selection now uses hysteresis across frames for stability
[0.1.1] - 2025-01-09
Added
- PyPI Publishing: Full wheel building and publishing workflow for TestPyPI and PyPI
embodik-sanitize-envCLI: Helper to sanitizeLD_LIBRARY_PATHfor clean pip installsembodik-examplesCLI: Tool to list and copy examples for pip-installed users- Built-in robot presets:
pandaandiiwapresets work withoutrobot_presets.yaml - sdist build support: Source distribution builds now auto-detect PyPI
pinwheel's Pinocchio
Changed
- Simplified
_runtime_deps.py- removed complex preloading logic, now just providesimport_pinocchio()helper - Documentation updated to recommend
venv + pip + unset LD_LIBRARY_PATHas primary user flow - ViserVisualizer now uses empty GeometryModel instead of None when collisions disabled
Fixed
- Fixed
KeyError: 'pinocchio/frames/universe'in ViserVisualizer whenload_collisions=False - Fixed RPATH for
_embodik_impl.soto correctly findlibembodik_core.so - Fixed sdist builds failing to find Pinocchio by auto-detecting
pinwheel's CMake config - Examples now work correctly for pip-installed users via
embodik-examples --copy
Dependencies
- Added
pin>=3.8.0to build-system requirements for sdist builds - Added
pyyaml,robot_descriptions,viser,yourdfpyto[examples]optional dependencies
[0.1.0] - 2025-12-12
Added
- Initial release of embodiK
- High-performance inverse kinematics solver with hierarchical task resolution
- Python bindings using nanobind
- Support for multiple task types (FrameTask, PostureTask, COMTask, JointTask, MultiJointTask)
- Position and velocity IK solving
- Optional visualization tools using Viser
- Comprehensive test suite
- Example scripts demonstrating various use cases
Technical Details
- C++17 core library built on Pinocchio and Eigen3
- Python 3.8+ support
- Linux x86_64 support
- CMake-based build system
- scikit-build-core for Python packaging