fix(ik): _random_q() silently produces garbage for non-finite joint limits - #648
Merged
Conversation
…imits _random_q() (used by every numeric IK solver -- IK_NR/IK_GN/IK_LM/IK_QP -- to seed random restarts) sampled directly from a joint's qlim with no check that the limits were actually finite. A joint with a bad (non-finite, e.g. inf/-inf) limit baked into its model's own data -- this was the real root cause behind #485's KinovaGen3 report, whose own qlim was fixed separately without ever patching this gap -- caused either a silent NaN joint value or an opaque internal numpy error (OverflowError: high - low range exceeds valid bounds, depending on which RNG code path is hit), instead of a clear diagnostic pointing at the actual bad joint. Now raises a ValueError naming the offending joint index(es) and their bad qlim before sampling, matching the existing convention elsewhere in this code (e.g. ETS.qlim already raises for an unset prismatic limit) of failing loudly rather than propagating a silent bad value. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #648 +/- ##
=====================================
Coverage 0.00% 0.00%
=====================================
Files 143 143
Lines 14035 14039 +4
=====================================
- Misses 14035 14039 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ik.py's _random_q() (fixed in the previous commit) and ik.cpp's own _rand_q() are separate implementations reached by different public entry points -- ikine_LM/ikine_NR/ikine_GN/ikine_QP go through the pure-Python IK_LM/IK_NR/IK_GN/IK_QP classes, while ik_LM/ik_NR/ik_GN (documented as "a fast solver implemented in C++") go through ik.cpp via nanobind. The C++ side had the identical gap with no guard at all: sampling a non-finite qlim via raw Eigen arithmetic silently produced a NaN q, which the solve loop then burned through every one of its random restarts on before returning a "failed" solution containing NaN -- no exception, no diagnostic. Since RTB's public API is "IK" regardless of which implementation backs a given method name, both solvers now enforce the same contract: _rand_q() throws nb::value_error (mapping to a Python ValueError, matching the Python-side message) before sampling if any joint's qlim is non-finite. Verified by rebuilding the compiled extension locally and confirming the fail-then-pass behavior directly: pre-fix, ets.ik_LM() silently returned (q=[nan], success=0) after 101 wasted searches; post-fix, it raises ValueError immediately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merged
petercorke
added a commit
that referenced
this pull request
Aug 26, 2026
… verify #379 repro # Conflicts: # tests/test_IK.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RTB has two separate IK implementations reachable from different public method names:
ikine_LM/ikine_NR/ikine_GN/ikine_QP-- the pure-PythonIK_LM/IK_NR/IK_GN/IK_QPclasses inIK.py.ik_LM/ik_NR/ik_GN-- documented as "a fast solver implemented in C++", backed byik.cppvia nanobind.Both had the identical gap: their random-restart sampling (
IK.py's_random_q()andik.cpp's own_rand_q()) sampled directly from a joint'sqlimwith no check that the limits were actually finite. A joint with a bad (non-finite) limit baked into its own model data -- this was the real root cause behind #485's KinovaGen3 report, whose ownqlimwas fixed separately without ever patching either solver -- caused:NaNjoint value, or an opaque internal numpy error (OverflowError: high - low range exceeds valid bounds), depending on which RNG code path is hit.q=[nan],success=0, after burning through every one of the 100 random restarts -- no exception, no diagnostic at all.Since RTB's public API is IK regardless of which implementation backs a given method name, both are fixed here, in lockstep.
Fix
IK.py's_random_q(): checksnp.isfinite()on the joint limits before sampling, raisesValueErrornaming the offending joint index(es) and their badqlim.ik.cpp's_rand_q(): same check viastd::isfinite, throwsnb::value_error(maps to a PythonValueError) before sampling.Both match the existing convention elsewhere in this code (e.g.
ETS.qlimalready raises for an unset prismatic limit) of failing loudly rather than propagating a silently-bad value.Test plan
test_random_q_rejects_non_finite_qlim(Python path): a joint withqlim=[-inf, inf]raisesValueError; a normal finite-limit joint is unaffected. Confirmed fails (opaqueOverflowError) against pre-fix code, passes with the fix.test_ik_lm_c_rejects_non_finite_qlim(C++ path):ets.ik_LM()with the same bad joint raisesValueError. Verified by rebuilding the compiled extension locally and confirming fail-then-pass directly -- pre-fix,ets.ik_LM()silently returned(q=[nan], success=0)after 101 wasted searches; post-fix, raises immediately.🤖 Generated with Claude Code