FA-88186 / Inverse kinematics solvers / Open access
Joint-centering objective pushes joints away from centre · case 01
Self-motion increases the distance to the preferred configuration.
ROOT CAUSE
The secondary velocity uses q - qc instead of qc - q.
VERIFIED REPAIR
Use z = k (qc - q).
Unsuccessful approach: Leaving the difference in degrees overshoots the centering by a factor of 57.
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,3)) for i in range(3)],
[sum(ls[j]*math.cos(cum[j]) for j in range(i,3)) 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(q[i]-qc[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]]], [['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['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]], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['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 | [-9.6761, 48.859, 73.9791] | [-9.6761, 48.859, 73.9791] | Passed |
| centering only | [26.3387, 33.3661, 48.8469] | [23.38, 40.6501, 42.2202] | Failed |
| task and centering | [-52.5967, 148.4425, -57.7815] | [-62.5399, 148.4425, -29.151] | Failed |
| straight arm singular | singular | singular | Passed |
| unequal links | [85.4024, -210.5325, 184.2439] | [87.5837, -214.0176, 178.4237] | Failed |
| negative config | [-22.2616, -29.7994, -111.6816] | [-7.4068, -55.9156, -92.0392] | Failed |
| strong centering | [70.3788, 39.2425, 94.0531] | [43.7121, 92.5758, 40.7197] | Failed |
SHA-256 / 4d76157c752c791c2ab9c8ad08d723cafdae0a5b7235fb375fe716ee78b67549
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,3)) 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*(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]]], [['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['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]], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['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 | [-9.6761, 48.859, 73.9791] | [-9.6761, 48.859, 73.9791] | Passed |
| centering only | [-59.9006, 245.6789, -144.3088] | [23.38, 40.6501, 42.2202] | Failed |
| task and centering | [-342.4221, 148.4425, 776.7376] | [-62.5399, 148.4425, -29.151] | Failed |
| straight arm singular | singular | singular | Passed |
| unequal links | [148.9825, -312.1156, 14.595] | [87.5837, -214.0176, 178.4237] | Failed |
| negative config | [410.7241, -791.0337, 460.852] | [-7.4068, -55.9156, -92.0392] | Failed |
| strong centering | [-706.8983, 1593.7966, -1460.501] | [43.7121, 92.5758, 40.7197] | Failed |
SHA-256 / dcac3818c9a475e2c364e3f94723ebbbe77cfbc31a47abe9950967f699f6bc93
3 / The verified repair
Exit 0"""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,3)) 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]]], [['task and centering', [[[1, 1, 1], [10, 40, -20], [2, 1], [0, 30, 30], 0.3]], [-62.5399, 148.4425, -29.151]], ['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]], ['unequal links', [[[2, 1, 0.5], [30, -45, 60], [1.5, 1], [0, 0, 0], 0.2]], [87.5837, -214.0176, 178.4237]], ['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 | [-9.6761, 48.859, 73.9791] | [-9.6761, 48.859, 73.9791] | Passed |
| centering only | [23.38, 40.6501, 42.2202] | [23.38, 40.6501, 42.2202] | Passed |
| task and centering | [-62.5399, 148.4425, -29.151] | [-62.5399, 148.4425, -29.151] | Passed |
| straight arm singular | singular | singular | Passed |
| unequal links | [87.5837, -214.0176, 178.4237] | [87.5837, -214.0176, 178.4237] | Passed |
| negative config | [-7.4068, -55.9156, -92.0392] | [-7.4068, -55.9156, -92.0392] | Passed |
| strong centering | [43.7121, 92.5758, 40.7197] | [43.7121, 92.5758, 40.7197] | Passed |
SHA-256 / e88d9b16de1ec44666b8df86c1843a3f1ee82f6ab5aaa6e40105ef9a09a38dbb
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.826793+00:00.
Case digest / a598383589122f6b828b45c8995fb92e70612223fc3950bb42ef46958128c1e0