FA-88176 / Inverse kinematics solvers / Open access
Redundant arm Jacobian sums links proximal to each joint · case 01
Joint velocities are computed for the wrong lever arms and the task error is not reduced.
ROOT CAUSE
Column i sums links 0..i instead of the links from joint i to the tip.
THE FAILURE
Column i sums links 0..i instead of the links from joint i to the tip.
Unsuccessful approach: Correcting only the x row leaves the y row summing the wrong links.
Case contract
Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.
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):
ls,q,t,qc,k=x
qr=[math.radians(v) for v in q]
cum=[sum(qr[:i+1]) for i in range(3)]
px=sum(ls[i]*math.cos(cum[i]) for i in range(3))
py=sum(ls[i]*math.sin(cum[i]) for i in range(3))
J=[[-sum(ls[j]*math.sin(cum[j]) for j in range(i+1)) for i in range(3)],
[sum(ls[j]*math.cos(cum[j]) for j in range(i+1)) for i in range(3)]]
a=sum(v*v for v in J[0])
b=sum(J[0][i]*J[1][i] for i in range(3))
c=sum(v*v for v in J[1])
det=a*c-b*b
if abs(det)<1e-12: return 'singular'
inv=[[c/det,-b/det],[-b/det,a/det]]
P=[[J[0][i]*inv[0][0]+J[1][i]*inv[1][0],J[0][i]*inv[0][1]+J[1][i]*inv[1][1]] for i in range(3)]
e=[t[0]-px,t[1]-py]
z=[k*math.radians(qc[i]-q[i]) for i in range(3)]
N=[[(1.0 if i==j else 0.0)-sum(P[i][r]*J[r][j] for r in range(2)) for j in range(3)] for i in range(3)]
dq=[P[i][0]*e[0]+P[i][1]*e[1]+sum(N[i][j]*z[j] for j in range(3)) for i in range(3)]
return [round(q[i]+math.degrees(dq[i]),4) for i in range(3)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]]], [['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]], [['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]], [['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]]], [['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]]]
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 |
|---|---|---|---|
| task only | [-8.9095, 5.1645, 59.9864] | [-9.6761, 48.859, 73.9791] | Failed |
| centering only | [9.777, 27.3913, 56.3852] | [23.38, 40.6501, 42.2202] | Failed |
| task and centering | [-98.4425, 46.0089, 8.8025] | [-62.5399, 148.4425, -29.151] | Failed |
| straight arm singular | singular | singular | Passed |
| unequal links | [208.632, -147.3671, 47.3363] | [87.5837, -214.0176, 178.4237] | Failed |
| negative config | [21.629, 0.221, -100.3191] | [-7.4068, -55.9156, -92.0392] | Failed |
| strong centering | [24.4696, 82.2348, 55.5304] | [43.7121, 92.5758, 40.7197] | Failed |
SHA-256 / 8c118f7fa339ab26d49db0e9f0c5ab71372f0cb4e7265218813f203bdacf283d
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
ls,q,t,qc,k=x
qr=[math.radians(v) for v in q]
cum=[sum(qr[:i+1]) for i in range(3)]
px=sum(ls[i]*math.cos(cum[i]) for i in range(3))
py=sum(ls[i]*math.sin(cum[i]) for i in range(3))
J=[[-sum(ls[j]*math.sin(cum[j]) for j in range(i,3)) for i in range(3)],
[sum(ls[j]*math.cos(cum[j]) for j in range(i+1)) for i in range(3)]]
a=sum(v*v for v in J[0])
b=sum(J[0][i]*J[1][i] for i in range(3))
c=sum(v*v for v in J[1])
det=a*c-b*b
if abs(det)<1e-12: return 'singular'
inv=[[c/det,-b/det],[-b/det,a/det]]
P=[[J[0][i]*inv[0][0]+J[1][i]*inv[1][0],J[0][i]*inv[0][1]+J[1][i]*inv[1][1]] for i in range(3)]
e=[t[0]-px,t[1]-py]
z=[k*math.radians(qc[i]-q[i]) for i in range(3)]
N=[[(1.0 if i==j else 0.0)-sum(P[i][r]*J[r][j] for r in range(2)) for j in range(3)] for i in range(3)]
dq=[P[i][0]*e[0]+P[i][1]*e[1]+sum(N[i][j]*z[j] for j in range(3)) for i in range(3)]
return [round(q[i]+math.degrees(dq[i]),4) for i in range(3)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]]], [['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]], [['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]], [['task only', [[[1, 1, 1], [20, 30, 40], [1.5, 1.5], [20, 30, 40], 0.0]], [-9.6761, 48.859, 73.9791]], ['centering only', [[[1, 1, 1], [20, 30, 40], [1.0911, 2.3209], [0, 0, 0], 0.5]], [23.38, 40.6501, 42.2202]], ['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]]], [['straight arm singular', [[[1, 1, 1], [0, 0, 0], [2, 1], [0, 0, 0], 0.5]], 'singular'], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['negative config', [[[1, 1.5, 0.7], [-30, -20, -60], [1, -2], [0, 10, -10], 1.0]], [-7.4068, -55.9156, -92.0392]], ['strong centering', [[[1, 1, 1], [60, 60, 60], [-1, 1.5], [0, 0, 0], 2.0]], [43.7121, 92.5758, 40.7197]], ['behind base', [[[1, 1, 1], [120, 30, 20], [-2, 0.5], [90, 0, 0], 0.4]], [75.3463, 86.5441, 80.8124]], ['small error', [[[1.2, 0.8, 0.5], [45, -30, 15], [1.6, 1.1], [45, -30, 15], 0.0]], [102.7802, -138.4201, 15.5818]], ['asymmetric centre', [[[1, 1, 1], [5, 80, -40], [1.2, 1.9], [10, 20, 30], 0.8]], [-22.1921, 110.3115, 3.6813]]]]
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 |
|---|---|---|---|
| task only | [41.6611, 22.0906, 13.031] | [-9.6761, 48.859, 73.9791] | Failed |
| centering only | [30.4129, 36.1096, 35.4131] | [23.38, 40.6501, 42.2202] | Failed |
| task and centering | [33.7449, 44.0835, -42.1457] | [-62.5399, 148.4425, -29.151] | Failed |
| straight arm singular | singular | singular | Passed |
| unequal links | [122.6636, -75.6553, 32.7289] | [87.5837, -214.0176, 178.4237] | Failed |
| negative config | [-45.4932, -30.4063, -28.5412] | [-7.4068, -55.9156, -92.0392] | Failed |
| strong centering | [70.1623, 39.6753, 78.3767] | [43.7121, 92.5758, 40.7197] | Failed |
SHA-256 / dc1c519f66074225d10b1c96e72805a92d2b4f691727b345c7f9e548e8ab68ab
HELD IN THE MEMBER ARCHIVE
The verified repair and its recorded checks are member-only.
This mechanism has 7 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.
Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.
Member access is invitation-based. Sign in with your invited account to inspect the repair.
Sign in to the archive ↗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:05.865778+00:00.
Case digest / 7ce6e13ad0dae9d001a6d056d95fd5dea75f5e4429a22a50319d1a08f9d95fd5