FA-88231 / Inverse kinematics solvers / Open access
Damping is applied outside the singular zone · case 01
Well-conditioned poses get damped because (1-w/w0)^2 is positive for w>w0.
ROOT CAUSE
The zone test is dropped and the quadratic profile is applied everywhere.
VERIFIED REPAIR
Set lam^2 = 0 when w >= w0.
Unsuccessful approach: Comparing w against w0 squared moves the zone boundary.
Case contract
Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".
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):
l1,l2,q1,q2,vx,vy,w0,lam0=x
a=math.radians(q1)
b=math.radians(q1+q2)
J=[[-l1*math.sin(a)-l2*math.sin(b),-l2*math.sin(b)],[l1*math.cos(a)+l2*math.cos(b),l2*math.cos(b)]]
w=abs(l1*l2*math.sin(math.radians(q2)))
lam2=lam0*lam0*(1.0-w/w0)**2
m11=J[0][0]**2+J[1][0]**2+lam2
m12=J[0][0]*J[0][1]+J[1][0]*J[1][1]
m22=J[0][1]**2+J[1][1]**2+lam2
det=m11*m22-m12*m12
if det<1e-12: return ['singular',round(w,6)]
g1=J[0][0]*vx+J[1][0]*vy
g2=J[0][1]*vx+J[1][1]*vy
d1=(m22*g1-m12*g2)/det
d2=(-m12*g1+m11*g2)/det
return ['damped' if lam2>0 else 'exact',round(math.degrees(d1),4),round(math.degrees(d2),4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]], [['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]]], [['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]], ['mid zone', [[1.5, 1, 20, 12, 0.0, -0.2, 0.6, 0.25]], ['damped', -11.2758, 17.8448]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]]]
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 |
|---|---|---|---|
| well conditioned | ['damped', 3.6855, -11.8059] | ['exact', 7.0591, -17.7507] | Failed |
| near singular damped | ['damped', 11.4896, -16.5029] | ['damped', 11.4896, -16.5029] | Passed |
| negative elbow near singular | ['damped', -5.2725, 18.1241] | ['damped', -5.2725, 18.1241] | Passed |
| negative elbow well conditioned | ['damped', 5.2116, -12.3508] | ['exact', 9.4468, -19.8957] | Failed |
| straight arm damped | ['damped', 6.8209, 3.4105] | ['damped', 6.8209, 3.4105] | Passed |
| straight arm undamped | ['singular', 0.0] | ['singular', 0.0] | Passed |
| folded arm | ['damped', -32.2582, 0.8237] | ['damped', -32.2582, 0.8237] | Passed |
SHA-256 / e3e5a116c73f4e7004c138cd3d41c0f4467604b46ab09664c9fcba970e33aaae
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
l1,l2,q1,q2,vx,vy,w0,lam0=x
a=math.radians(q1)
b=math.radians(q1+q2)
J=[[-l1*math.sin(a)-l2*math.sin(b),-l2*math.sin(b)],[l1*math.cos(a)+l2*math.cos(b),l2*math.cos(b)]]
w=abs(l1*l2*math.sin(math.radians(q2)))
lam2=0.0 if w>=w0*w0 else lam0*lam0*(1.0-w/w0)**2
m11=J[0][0]**2+J[1][0]**2+lam2
m12=J[0][0]*J[0][1]+J[1][0]*J[1][1]
m22=J[0][1]**2+J[1][1]**2+lam2
det=m11*m22-m12*m12
if det<1e-12: return ['singular',round(w,6)]
g1=J[0][0]*vx+J[1][0]*vy
g2=J[0][1]*vx+J[1][1]*vy
d1=(m22*g1-m12*g2)/det
d2=(-m12*g1+m11*g2)/det
return ['damped' if lam2>0 else 'exact',round(math.degrees(d1),4),round(math.degrees(d2),4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]], [['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]]], [['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]], ['mid zone', [[1.5, 1, 20, 12, 0.0, -0.2, 0.6, 0.25]], ['damped', -11.2758, 17.8448]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]]]
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 |
|---|---|---|---|
| well conditioned | ['exact', 7.0591, -17.7507] | ['exact', 7.0591, -17.7507] | Passed |
| near singular damped | ['damped', 11.4896, -16.5029] | ['damped', 11.4896, -16.5029] | Passed |
| negative elbow near singular | ['damped', -5.2725, 18.1241] | ['damped', -5.2725, 18.1241] | Passed |
| negative elbow well conditioned | ['exact', 9.4468, -19.8957] | ['exact', 9.4468, -19.8957] | Passed |
| straight arm damped | ['damped', 6.8209, 3.4105] | ['damped', 6.8209, 3.4105] | Passed |
| straight arm undamped | ['singular', 0.0] | ['singular', 0.0] | Passed |
| folded arm | ['exact', -45.7787, 0.8777] | ['damped', -32.2582, 0.8237] | Failed |
SHA-256 / fe9b6bacb868b746050c02dbdd71695a2e098c1be018772932de8658ec3b3ad4
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
l1,l2,q1,q2,vx,vy,w0,lam0=x
a=math.radians(q1)
b=math.radians(q1+q2)
J=[[-l1*math.sin(a)-l2*math.sin(b),-l2*math.sin(b)],[l1*math.cos(a)+l2*math.cos(b),l2*math.cos(b)]]
w=abs(l1*l2*math.sin(math.radians(q2)))
lam2=0.0 if w>=w0 else lam0*lam0*(1.0-w/w0)**2
m11=J[0][0]**2+J[1][0]**2+lam2
m12=J[0][0]*J[0][1]+J[1][0]*J[1][1]
m22=J[0][1]**2+J[1][1]**2+lam2
det=m11*m22-m12*m12
if det<1e-12: return ['singular',round(w,6)]
g1=J[0][0]*vx+J[1][0]*vy
g2=J[0][1]*vx+J[1][1]*vy
d1=(m22*g1-m12*g2)/det
d2=(-m12*g1+m11*g2)/det
return ['damped' if lam2>0 else 'exact',round(math.degrees(d1),4),round(math.degrees(d2),4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]], [['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]]], [['inside zone no damping gain', [[1, 1, 45, 10, 0.2, -0.1, 0.5, 0.0]], ['exact', 10.8225, -34.1537]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['long links', [[2, 1.5, -20, 40, 0.3, 0.3, 0.5, 0.3]], ['exact', 17.1371, -27.792]], ['exact threshold', [[1, 2, 10, 30, 0.1, 0.1, 1.0, 0.4]], ['damped', 16.144, -22.7815]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]], ['large damping', [[1, 1, 90, 8, -0.1, 0.0, 0.8, 0.6]], ['damped', 2.2347, 0.9844]], ['slightly above threshold', [[1, 1, 0, 20, 0.2, 0.2, 0.3, 0.5]], ['exact', 42.9429, -76.4473]], ['small straight damping', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0002]], ['damped', 6.8755, 3.4377]], ['mid zone', [[1.5, 1, 20, 12, 0.0, -0.2, 0.6, 0.25]], ['damped', -11.2758, 17.8448]]], [['well conditioned', [[1, 1, 30, 90, 0.1, 0.2, 0.3, 0.2]], ['exact', 7.0591, -17.7507]], ['near singular damped', [[1, 1, 30, 5, 0.1, 0.2, 0.3, 0.2]], ['damped', 11.4896, -16.5029]], ['negative elbow near singular', [[1, 1, 30, -5, 0.1, 0.2, 0.3, 0.2]], ['damped', -5.2725, 18.1241]], ['negative elbow well conditioned', [[1, 1, 10, -80, -0.2, 0.1, 0.3, 0.2]], ['exact', 9.4468, -19.8957]], ['straight arm damped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.2]], ['damped', 6.8209, 3.4105]], ['straight arm undamped', [[1, 1, 0, 0, 0.0, 0.3, 0.3, 0.0]], ['singular', 0.0]], ['folded arm', [[1, 1, 60, 175, 0.05, 0.05, 0.2, 0.1]], ['damped', -32.2582, 0.8237]]]]
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 |
|---|---|---|---|
| well conditioned | ['exact', 7.0591, -17.7507] | ['exact', 7.0591, -17.7507] | Passed |
| near singular damped | ['damped', 11.4896, -16.5029] | ['damped', 11.4896, -16.5029] | Passed |
| negative elbow near singular | ['damped', -5.2725, 18.1241] | ['damped', -5.2725, 18.1241] | Passed |
| negative elbow well conditioned | ['exact', 9.4468, -19.8957] | ['exact', 9.4468, -19.8957] | Passed |
| straight arm damped | ['damped', 6.8209, 3.4105] | ['damped', 6.8209, 3.4105] | Passed |
| straight arm undamped | ['singular', 0.0] | ['singular', 0.0] | Passed |
| folded arm | ['damped', -32.2582, 0.8237] | ['damped', -32.2582, 0.8237] | Passed |
SHA-256 / 4d031b967595c772684b74433f2a112f47c0be602672d224cdab273b02654cdc
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:06.253105+00:00.
Case digest / d40ab0eb6827f64eebd72b59caabb65677024fccb106d27697956f3afb1bccf2