Skip to content

fix(ik): _random_q() silently produces garbage for non-finite joint limits - #648

Merged
petercorke merged 2 commits into
mainfrom
fix/random-q-nan-guard
Aug 26, 2026
Merged

fix(ik): _random_q() silently produces garbage for non-finite joint limits#648
petercorke merged 2 commits into
mainfrom
fix/random-q-nan-guard

Conversation

@petercorke

@petercorke petercorke commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

RTB has two separate IK implementations reachable from different public method names:

  • ikine_LM/ikine_NR/ikine_GN/ikine_QP -- the pure-Python IK_LM/IK_NR/IK_GN/IK_QP classes in IK.py.
  • ik_LM/ik_NR/ik_GN -- documented as "a fast solver implemented in C++", backed by ik.cpp via nanobind.

Both had the identical gap: their random-restart sampling (IK.py's _random_q() and ik.cpp's own _rand_q()) sampled directly from a joint's qlim with 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 own qlim was fixed separately without ever patching either solver -- caused:

  • Python path: 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.
  • C++ path: silently returned a "solution" with 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(): checks np.isfinite() on the joint limits before sampling, raises ValueError naming the offending joint index(es) and their bad qlim.
  • ik.cpp's _rand_q(): same check via std::isfinite, throws nb::value_error (maps to a Python ValueError) before sampling.

Both match 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 silently-bad value.

Test plan

  • New regression test test_random_q_rejects_non_finite_qlim (Python path): a joint with qlim=[-inf, inf] raises ValueError; a normal finite-limit joint is unaffected. Confirmed fails (opaque OverflowError) against pre-fix code, passes with the fix.
  • New regression test test_ik_lm_c_rejects_non_finite_qlim (C++ path): ets.ik_LM() with the same bad joint raises ValueError. 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.
  • Full test suite green: 640 passed, 75 skipped (unrelated optional-dep skips), no regressions -- run against the freshly rebuilt extension.

🤖 Generated with Claude Code

…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

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 0.00%. Comparing base (5b5a0fe) to head (562566f).

Files with missing lines Patch % Lines
src/roboticstoolbox/robot/IK.py 0.00% 6 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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>
@petercorke
petercorke merged commit 2f43a3c into main Aug 26, 2026
23 checks passed
@petercorke
petercorke deleted the fix/random-q-nan-guard branch August 26, 2026 10:59
@github-actions github-actions Bot mentioned this pull request Aug 26, 2026
petercorke added a commit that referenced this pull request Aug 26, 2026
… verify #379 repro

# Conflicts:
#	tests/test_IK.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant