FA-88021 / Inverse kinematics solvers / Open access
Selected branch index refers to the filtered list · case 01
When an infeasible candidate precedes the winner, the reported index points at the wrong solution.
ROOT CAUSE
Candidates are filtered first and enumerated afterwards, so indices shift.
VERIFIED REPAIR
Enumerate the original candidate list and skip infeasible entries.
Unsuccessful approach: Starting the enumeration at 1 reports one-based indices.
Case contract
Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".
Why this case matters
Inverse kinematics code turns task-space goals into joint commands; a wrong branch, sign, limit or update order sends a real arm to the wrong pose.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
cands,cur,lims,w=x
best=None
bi=None
for k,c in enumerate([c for c in cands if all(l is None or l[0]<=q<=l[1] for q,l in zip(c,lims))]):
ok=all(l is None or l[0]<=q<=l[1] for q,l in zip(c,lims))
if not ok: continue
cost=0.0
for q,p,wi,l in zip(c,cur,w,lims):
if l is None:
d=abs(q-p)%360.0
d=min(d,360.0-d)
else:
d=abs(q-p)
cost+=wi*d*d
if best is None or cost<best-1e-9:
best=cost
bi=k
if bi is None: return 'none'
return [bi,round(best,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['exact tie keeps first', [[[[10, 0], [-10, 0]], [0, 0], [[-90, 90], None], [1, 1]]], [0, 100.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['lower bound inclusive', [[[[-90, 0], [60, 60]], [-80, 0], [[-90, 90], [-90, 90]], [1, 1]]], [0, 100.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]], ['heavy weight flips choice', [[[[5, 40], [30, 0]], [0, 0], [None, None], [10, 1]]], [0, 1850.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| limit boundary candidate | [0, 100.0] | [0, 100.0] | Passed |
| all infeasible | none | none | Passed |
| continuous wrap shorter | [0, 25.0] | [0, 25.0] | Passed |
| multi-turn limited joint | [1, 400.0] | [1, 400.0] | Passed |
| wide limited joint far | [1, 400.0] | [1, 400.0] | Passed |
| weighted wrist | [0, 350.0] | [0, 350.0] | Passed |
| infeasible first candidate | [1, 425.0] | [2, 425.0] | Failed |
SHA-256 / 0040b232cb1f6ce768808f8fa318bc6f23cf76943509077bc0457499ffd117ed
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
cands,cur,lims,w=x
best=None
bi=None
for k,c in enumerate(cands,1):
ok=all(l is None or l[0]<=q<=l[1] for q,l in zip(c,lims))
if not ok: continue
cost=0.0
for q,p,wi,l in zip(c,cur,w,lims):
if l is None:
d=abs(q-p)%360.0
d=min(d,360.0-d)
else:
d=abs(q-p)
cost+=wi*d*d
if best is None or cost<best-1e-9:
best=cost
bi=k
if bi is None: return 'none'
return [bi,round(best,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['exact tie keeps first', [[[[10, 0], [-10, 0]], [0, 0], [[-90, 90], None], [1, 1]]], [0, 100.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['lower bound inclusive', [[[[-90, 0], [60, 60]], [-80, 0], [[-90, 90], [-90, 90]], [1, 1]]], [0, 100.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]], ['heavy weight flips choice', [[[[5, 40], [30, 0]], [0, 0], [None, None], [10, 1]]], [0, 1850.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| limit boundary candidate | [1, 100.0] | [0, 100.0] | Failed |
| all infeasible | none | none | Passed |
| continuous wrap shorter | [1, 25.0] | [0, 25.0] | Failed |
| multi-turn limited joint | [2, 400.0] | [1, 400.0] | Failed |
| wide limited joint far | [2, 400.0] | [1, 400.0] | Failed |
| weighted wrist | [1, 350.0] | [0, 350.0] | Failed |
| infeasible first candidate | [3, 425.0] | [2, 425.0] | Failed |
SHA-256 / e37386eb115c4cff6c898abe77fece5bde173fe8d972a9bcb7d366768d9769c3
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
cands,cur,lims,w=x
best=None
bi=None
for k,c in enumerate(cands):
ok=all(l is None or l[0]<=q<=l[1] for q,l in zip(c,lims))
if not ok: continue
cost=0.0
for q,p,wi,l in zip(c,cur,w,lims):
if l is None:
d=abs(q-p)%360.0
d=min(d,360.0-d)
else:
d=abs(q-p)
cost+=wi*d*d
if best is None or cost<best-1e-9:
best=cost
bi=k
if bi is None: return 'none'
return [bi,round(best,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['exact tie keeps first', [[[[10, 0], [-10, 0]], [0, 0], [[-90, 90], None], [1, 1]]], [0, 100.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]]], [['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['near tie within tolerance', [[[[10, 0], [0, 10.0000000001]], [0, 0], [None, None], [1, 1]]], [0, 100.0]], ['slightly better later', [[[[10, 0], [9.999, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 1]]], [1, 99.98]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['lower bound inclusive', [[[[-90, 0], [60, 60]], [-80, 0], [[-90, 90], [-90, 90]], [1, 1]]], [0, 100.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['wide limited joint far', [[[[250, 0], [-100, 0]], [-120, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous across 180', [[[[0, -179], [0, 100]], [0, 179], [None, None], [1, 1]]], [0, 4.0]], ['three joints mixed', [[[[10, 350, -20], [15, -5, -25], [100, 0, 0]], [12, 0, -22], [[-120, 120], None, [-45, 45]], [2, 1, 1]]], [1, 52.0]], ['heavy weight flips choice', [[[[5, 40], [30, 0]], [0, 0], [None, None], [10, 1]]], [0, 1850.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]], [['limit boundary candidate', [[[[170, 10], [0, 10]], [160, 10], [[-170, 170], None], [1, 1]]], [0, 100.0]], ['all infeasible', [[[[200, 0], [-200, 0]], [0, 0], [[-170, 170], None], [1, 1]]], 'none'], ['continuous wrap shorter', [[[[0, 170], [0, -170]], [0, 175], [[-90, 90], None], [1, 1]]], [0, 25.0]], ['multi-turn limited joint', [[[[-200, 0], [150, 0]], [170, 0], [[-270, 270], None], [1, 1]]], [1, 400.0]], ['weighted wrist', [[[[10, 50], [40, 0]], [0, 0], [[-90, 90], [-90, 90]], [1, 0.1]]], [0, 350.0]], ['infeasible first candidate', [[[[95, 0], [30, 0], [20, 5]], [0, 0], [[-90, 90], None], [1, 1]]], [2, 425.0]], ['continuous large difference', [[[[0, 300], [0, 200]], [0, 0], [[-10, 10], None], [1, 1]]], [0, 3600.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| limit boundary candidate | [0, 100.0] | [0, 100.0] | Passed |
| all infeasible | none | none | Passed |
| continuous wrap shorter | [0, 25.0] | [0, 25.0] | Passed |
| multi-turn limited joint | [1, 400.0] | [1, 400.0] | Passed |
| wide limited joint far | [1, 400.0] | [1, 400.0] | Passed |
| weighted wrist | [0, 350.0] | [0, 350.0] | Passed |
| infeasible first candidate | [2, 425.0] | [2, 425.0] | Passed |
SHA-256 / 5a805e001416611d4af7d921a4ac9e735e2a42acbe386cfd00c8fe56ad047f3f
Verification & scope
Deterministic planar or low-dimensional teaching model with a stipulated convention; not a general robotics library. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:51:04.303690+00:00.
Case digest / be303b1bbef1febffcd99c3df16cc894b7fb933004d8fbb9acab38e33bba1789