Summary
The native C++ HP pencil eigensolve (solve_pencil_eigenvalues in cpp/include/ssik_cpp/solvers/husty_pfurner.hpp) reduces the 80x80 matrix-polynomial eigenproblem to a standard eigenproblem via the monic Frobenius companion (necessary because Eigen's RealQZ generalized eigensolver non-converges on ~38% of these pencils, per #538). On ill-conditioned pencils this recovers fewer real roots than Python's reference, which uses LAPACK's generalized eigensolver (dggev) on the pencil directly.
Impact (bounded, non-shipping)
- No soundness impact: every native HP solution FK-closes (verified:
bad_fk=0, worst FK ~1e-7 across jaco2/piper).
- The completeness gap manifests only at pathologically ill-conditioned general-6R DH (e.g. jaco2, piper as raw 6R), where the native solve recovers ~63-82% of the Python-oracle IK branches.
- HP is never dispatched for general 6R in production (those route to RR / ikgeo tier-2; see the HP-keep decision). HP's actual dispatch target is symmetric-DH locked-7R sub-chains, which are well-conditioned -- there the native kernel is verified oracle-complete (0 missing over clean Tv6 and Tv4 DH, 60 poses each; true root recovered every time).
Root cause
Forming S[d]^-1 (monic reduction) amplifies error when the leading coefficient is ill-conditioned; a real root then emerges slightly-complex and is filtered. Orientation selection (forward vs reversed companion) and unioning both orientations were tried and do not close the gap (the roots are absent from both companions' spectra). Loosening real_tol 1e-3 -> 1e-2 recovers only a few. The remaining roots are simply not eigenvalues of either monic companion at float64 precision.
Candidate fix
Call LAPACK dggev (generalized eigenvalues of the pencil A - lambda B, no monic reduction) from C++ -- matches the Python reference exactly and is robust where RealQZ is not. Trade-off: adds a LAPACK link dependency to the otherwise self-contained artifact (MoveIt/C++ consumers generally already link BLAS/LAPACK). Alternatively, a balancing/equilibration pre-step on the pencil before the monic reduction.
Acceptance
Native HP recovers >= the Python-oracle IK branch set on the degenerate general-6R poses (jaco2, piper) at the eliminate boundary, while preserving convergence robustness on the locked-7R dispatch path.
Summary
The native C++ HP pencil eigensolve (
solve_pencil_eigenvaluesincpp/include/ssik_cpp/solvers/husty_pfurner.hpp) reduces the 80x80 matrix-polynomial eigenproblem to a standard eigenproblem via the monic Frobenius companion (necessary because Eigen'sRealQZgeneralized eigensolver non-converges on ~38% of these pencils, per #538). On ill-conditioned pencils this recovers fewer real roots than Python's reference, which uses LAPACK's generalized eigensolver (dggev) on the pencil directly.Impact (bounded, non-shipping)
bad_fk=0, worst FK ~1e-7 across jaco2/piper).Root cause
Forming
S[d]^-1(monic reduction) amplifies error when the leading coefficient is ill-conditioned; a real root then emerges slightly-complex and is filtered. Orientation selection (forward vs reversed companion) and unioning both orientations were tried and do not close the gap (the roots are absent from both companions' spectra). Looseningreal_tol1e-3 -> 1e-2 recovers only a few. The remaining roots are simply not eigenvalues of either monic companion at float64 precision.Candidate fix
Call LAPACK
dggev(generalized eigenvalues of the pencilA - lambda B, no monic reduction) from C++ -- matches the Python reference exactly and is robust whereRealQZis not. Trade-off: adds a LAPACK link dependency to the otherwise self-contained artifact (MoveIt/C++ consumers generally already link BLAS/LAPACK). Alternatively, a balancing/equilibration pre-step on the pencil before the monic reduction.Acceptance
Native HP recovers >= the Python-oracle IK branch set on the degenerate general-6R poses (jaco2, piper) at the eliminate boundary, while preserving convergence robustness on the locked-7R dispatch path.