FA-87966 / Inverse kinematics solvers / Open access
FABRIK reachability is measured from the current tip · case 01
Reachable targets are stretched toward as unreachable, and far targets near the tip enter the iteration.
ROOT CAUSE
The reach test uses the distance from the end effector to the target instead of from the fixed base.
VERIFIED REPAIR
Compare the base-to-target distance with the total chain length.
Unsuccessful approach: Comparing against the longest single link instead of the sum rejects many reachable targets.
Case contract
Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.
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):
pts,t,tol,iters=x
p=[[float(v) for v in q] for q in pts]
d=[math.dist(p[i],p[i+1]) for i in range(len(p)-1)]
base=p[0][:]
if math.dist(p[-1],t)>sum(d):
for i in range(len(d)):
r=math.dist(p[i],t)
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*t[0],(1-lam)*p[i][1]+lam*t[1]]
return ['unreachable',[[round(v,4) for v in q] for q in p]]
count=0
while math.dist(p[-1],t)>tol and count<iters:
count+=1
p[-1]=[float(t[0]),float(t[1])]
for i in range(len(p)-2,-1,-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i]=[(1-lam)*p[i+1][0]+lam*p[i][0],(1-lam)*p[i+1][1]+lam*p[i][1]]
p[0]=base[:]
for i in range(len(p)-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*p[i+1][0],(1-lam)*p[i][1]+lam*p[i+1][1]]
status='reached' if math.dist(p[-1],t)<=tol else 'max-iterations'
return [status,count,[[round(v,4) for v in q] for q in p]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]]], [['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['tip near base unequal', [[[[0, 0], [3, 0], [4, 0]], [2, 0.5], 0.001, 20]], ['max-iterations', 20, [[0.0, 0.0], [2.9861, 0.2886], [2.0083, 0.4982]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.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 |
|---|---|---|---|
| two links bend up | ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]] | ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]] | Passed |
| unequal links | ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]] | ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]] | Passed |
| three unequal links | ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]] | ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]] | Passed |
| unreachable along x | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Passed |
| unreachable diagonal unequal | ['max-iterations', 10, [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | Failed |
| single link one pass | ['unreachable', [[0.0, 0.0], [0.0, 2.0]]] | ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]] | Failed |
| already on target zero tol | ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Passed |
SHA-256 / 5bfea9858402cd908076948715b276bab54ce791dde2e3b2d725a79e96f0fbf0
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
pts,t,tol,iters=x
p=[[float(v) for v in q] for q in pts]
d=[math.dist(p[i],p[i+1]) for i in range(len(p)-1)]
base=p[0][:]
if math.dist(base,t)>max(d):
for i in range(len(d)):
r=math.dist(p[i],t)
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*t[0],(1-lam)*p[i][1]+lam*t[1]]
return ['unreachable',[[round(v,4) for v in q] for q in p]]
count=0
while math.dist(p[-1],t)>tol and count<iters:
count+=1
p[-1]=[float(t[0]),float(t[1])]
for i in range(len(p)-2,-1,-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i]=[(1-lam)*p[i+1][0]+lam*p[i][0],(1-lam)*p[i+1][1]+lam*p[i][1]]
p[0]=base[:]
for i in range(len(p)-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*p[i+1][0],(1-lam)*p[i][1]+lam*p[i+1][1]]
status='reached' if math.dist(p[-1],t)<=tol else 'max-iterations'
return [status,count,[[round(v,4) for v in q] for q in p]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]]], [['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['tip near base unequal', [[[[0, 0], [3, 0], [4, 0]], [2, 0.5], 0.001, 20]], ['max-iterations', 20, [[0.0, 0.0], [2.9861, 0.2886], [2.0083, 0.4982]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.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 |
|---|---|---|---|
| two links bend up | ['unreachable', [[0.0, 0.0], [0.7071, 0.7071], [1.4142, 1.4142]]] | ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]] | Failed |
| unequal links | ['unreachable', [[0.0, 0.0], [0.8944, 1.7889], [1.3416, 2.6833]]] | ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]] | Failed |
| three unequal links | ['unreachable', [[0.0, 0.0], [0.7428, 1.857], [0.9285, 2.3212], [1.4856, 3.7139]]] | ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]] | Failed |
| unreachable along x | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Passed |
| unreachable diagonal unequal | ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | Passed |
| single link one pass | ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]] | ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]] | Passed |
| already on target zero tol | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Failed |
SHA-256 / dbc4be21112875ab272965b006553547bba01c5519e360cd3f3a7d6be66d56c8
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
pts,t,tol,iters=x
p=[[float(v) for v in q] for q in pts]
d=[math.dist(p[i],p[i+1]) for i in range(len(p)-1)]
base=p[0][:]
if math.dist(base,t)>sum(d):
for i in range(len(d)):
r=math.dist(p[i],t)
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*t[0],(1-lam)*p[i][1]+lam*t[1]]
return ['unreachable',[[round(v,4) for v in q] for q in p]]
count=0
while math.dist(p[-1],t)>tol and count<iters:
count+=1
p[-1]=[float(t[0]),float(t[1])]
for i in range(len(p)-2,-1,-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i]=[(1-lam)*p[i+1][0]+lam*p[i][0],(1-lam)*p[i+1][1]+lam*p[i][1]]
p[0]=base[:]
for i in range(len(p)-1):
r=math.dist(p[i+1],p[i])
lam=d[i]/r
p[i+1]=[(1-lam)*p[i][0]+lam*p[i+1][0],(1-lam)*p[i][1]+lam*p[i+1][1]]
status='reached' if math.dist(p[-1],t)<=tol else 'max-iterations'
return [status,count,[[round(v,4) for v in q] for q in p]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['single link one pass', [[[[0, 0], [2, 0]], [0, 2], 1e-06, 1]], ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]]], [['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['offset base', [[[[1, 1], [2, 1], [3, 1]], [2, 2.5], 0.001, 10]], ['reached', 6, [[1.0, 1.0], [1.8605, 1.5094], [1.9999, 2.4996]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['tip near base unequal', [[[[0, 0], [3, 0], [4, 0]], [2, 0.5], 0.001, 20]], ['max-iterations', 20, [[0.0, 0.0], [2.9861, 0.2886], [2.0083, 0.4982]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['already on target zero tol', [[[[0, 0], [1, 0], [2, 0]], [2, 0], 0, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['near full stretch reachable', [[[[0, 0], [0, 1], [0, 3]], [2.9, 0.2], 0.001, 15]], ['reached', 15, [[0.0, 0.0], [0.9105, 0.4134], [2.8991, 0.2001]]]], ['unreachable from offset base', [[[[2, 0], [2, 1], [2, 3]], [2, -4], 0.001, 10]], ['unreachable', [[2.0, 0.0], [2.0, -1.0], [2.0, -3.0]]]], ['start already inside tolerance', [[[[0, 0], [1, 1], [2, 1]], [2, 1.05], 0.1, 5]], ['reached', 0, [[0.0, 0.0], [1.0, 1.0], [2.0, 1.0]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]]], [['two links bend up', [[[[0, 0], [1, 0], [2, 0]], [1, 1], 0.001, 10]], ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]]], ['unequal links', [[[[0, 0], [2, 0], [3, 0]], [1, 2], 0.001, 10]], ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]]], ['three unequal links', [[[[0, 0], [2, 0], [2.5, 0], [4, 0]], [1, 2.5], 0.001, 20]], ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]]], ['unreachable along x', [[[[0, 0], [1, 0], [2, 0]], [5, 0], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]]], ['unreachable diagonal unequal', [[[[0, 0], [2, 1], [3, 1]], [4, 4], 0.001, 10]], ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]]], ['pass budget exhausted', [[[[0, 0], [1, 0], [1.5, 0], [3, 0]], [-1, 1.5], 1e-09, 2]], ['max-iterations', 2, [[0.0, 0.0], [0.809, 0.5878], [0.3308, 0.7337], [-0.9691, 1.4822]]]], ['zero passes allowed', [[[[0, 0], [1, 0], [2, 0]], [0, 2], 0.001, 0]], ['max-iterations', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.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 |
|---|---|---|---|
| two links bend up | ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]] | ['reached', 1, [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]] | Passed |
| unequal links | ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]] | ['reached', 2, [[0.0, 0.0], [1.6, 1.2], [1.0, 2.0]]] | Passed |
| three unequal links | ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]] | ['reached', 1, [[0.0, 0.0], [1.8649, 0.7227], [1.7717, 1.2139], [0.9999, 2.5001]]] | Passed |
| unreachable along x | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['unreachable', [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Passed |
| unreachable diagonal unequal | ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | ['unreachable', [[0.0, 0.0], [1.5811, 1.5811], [2.2882, 2.2882]]] | Passed |
| single link one pass | ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]] | ['reached', 1, [[0.0, 0.0], [0.0, 2.0]]] | Passed |
| already on target zero tol | ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | ['reached', 0, [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]] | Passed |
SHA-256 / 9bac0f1278e77d16481f0ff1bcf9a3caa6be88bf41177f4297553218602d6a93
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:03.795350+00:00.
Case digest / b3f09f23dc1eb39a4c48be576557b304fe3a663d2daeb16aa87e2f29e58f2324