FAILURE MAP
← Case archive

FA-88331 / Inverse kinematics solvers / Open access

Adaptive gain numerator uses the squared error norm · case 01

The step gain ignores how well the predicted motion aligns with the error.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

alpha is computed as |e|^2/|J J^T e|^2 rather than <e, J J^T e>/|J J^T e|^2.

THE FAILURE

alpha is computed as |e|^2/|J J^T e|^2 rather than <e, J J^T e>/|J J^T e|^2.

Unsuccessful approach: Using |J J^T e| in the numerator gives a scale-dependent gain.

Case contract

Input [l1,l2,q1,q2,tx,ty]: one Jacobian-transpose step for a planar 2R arm with the error-optimal gain alpha = <e, J J^T e> / <J J^T e, J J^T e>, dq = alpha J^T e. When <J J^T e, J J^T e> < 1e-15 return ["stationary", q1, q2]. Otherwise return ["step", alpha (6 decimals), q1 and q2 updated in degrees (4 decimals)].

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,tx,ty=x
    a=math.radians(q1)
    b=math.radians(q1+q2)
    ex=tx-(l1*math.cos(a)+l2*math.cos(b))
    ey=ty-(l1*math.sin(a)+l2*math.sin(b))
    j11=-l1*math.sin(a)-l2*math.sin(b)
    j12=-l2*math.sin(b)
    j21=l1*math.cos(a)+l2*math.cos(b)
    j22=l2*math.cos(b)
    g1=j11*ex+j21*ey
    g2=j12*ex+j22*ey
    h1=j11*g1+j12*g2
    h2=j21*g1+j22*g2
    den=h1*h1+h2*h2
    if den<1e-15: return ['stationary',q1,q2]
    alpha=(ex*ex+ey*ey)/den
    return ['step',round(alpha,6),round(q1+math.degrees(alpha*g1),4),round(q2+math.degrees(alpha*g2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 0, 0]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 0, 0]]], [['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 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 fixtureActualExpectedOutcome
moderate error['step', 0.070435, 32.5652, 61.4771]['step', 0.263207, 39.5859, 65.5199]Failed
at target['stationary', 0, 90]['stationary', 0, 90]Passed
large error['step', 0.54076, 43.4411, 104.1529]['step', 0.287793, 23.1194, 76.4812]Failed
unequal links['step', 0.018566, 25.452, -38.8226]['step', 0.11371, 53.3906, -32.7888]Failed
negative angles['step', 0.034012, -32.3347, -60.9725]['step', 0.183895, -42.6234, -65.2581]Failed
behind base['step', 0.113811, 137.8154, 42.1681]['step', 0.217338, 154.0209, 53.2367]Failed
small error['step', 0.04059, 10.4068, 10.2038]['step', 0.201469, 12.0193, 11.0115]Failed

SHA-256 / 0910ffbd6ce7f9164c3d2bea1f9d1d6df68d6933b2c5f5e7c05e02fd8808c98d

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,tx,ty=x
    a=math.radians(q1)
    b=math.radians(q1+q2)
    ex=tx-(l1*math.cos(a)+l2*math.cos(b))
    ey=ty-(l1*math.sin(a)+l2*math.sin(b))
    j11=-l1*math.sin(a)-l2*math.sin(b)
    j12=-l2*math.sin(b)
    j21=l1*math.cos(a)+l2*math.cos(b)
    j22=l2*math.cos(b)
    g1=j11*ex+j21*ey
    g2=j12*ex+j22*ey
    h1=j11*g1+j12*g2
    h2=j21*g1+j22*g2
    den=h1*h1+h2*h2
    if den<1e-15: return ['stationary',q1,q2]
    alpha=math.sqrt(den)/den
    return ['step',round(alpha,6),round(q1+math.degrees(alpha*g1),4),round(q2+math.degrees(alpha*g2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 0, 0]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 0, 0]]], [['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['large error', [[1, 1, 0, 45, -1.5, 0.2]], ['step', 0.287793, 23.1194, 76.4812]], ['unequal links', [[2, 1, 20, -40, 1.5, 2.0]], ['step', 0.11371, 53.3906, -32.7888]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]]], [['moderate error', [[1, 1, 30, 60, 0.5, 1.6]], ['step', 0.263207, 39.5859, 65.5199]], ['at target', [[1, 1, 0, 90, 1, 1]], ['stationary', 0, 90]], ['negative angles', [[1.5, 1, -30, -60, 0.8, -2]], ['step', 0.183895, -42.6234, -65.2581]], ['behind base', [[1, 1, 120, 30, -1, -1]], ['step', 0.217338, 154.0209, 53.2367]], ['small error', [[1, 1, 10, 10, 1.9, 0.6]], ['step', 0.201469, 12.0193, 11.0115]], ['long forearm', [[0.5, 2, 70, -20, -0.5, 2.2]], ['step', 0.098879, 93.8254, -1.5747]], ['straight arm radial error', [[1, 1, 0, 0, 1.5, 0]], ['stationary', 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 fixtureActualExpectedOutcome
moderate error['step', 0.699442, 55.4733, 74.6685]['step', 0.263207, 39.5859, 65.5199]Failed
at target['stationary', 0, 90]['stationary', 0, 90]Passed
large error['step', 0.226478, 18.1938, 69.7741]['step', 0.287793, 23.1194, 76.4812]Failed
unequal links['step', 0.064313, 38.8852, -35.9215]['step', 0.11371, 53.3906, -32.7888]Failed
negative angles['step', 0.330414, -52.6812, -69.4475]['step', 0.183895, -42.6234, -65.2581]Failed
behind base['step', 0.140908, 142.0571, 45.0653]['step', 0.217338, 154.0209, 53.2367]Failed
small error['step', 2.294165, 32.9939, 21.518]['step', 0.201469, 12.0193, 11.0115]Failed

SHA-256 / ab171225c237a9bc3f0c905b938f8f873c035b586faf610d7dc5fdeb2bb596d7

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:07.144645+00:00.

Case digest / 1cffdb921ebd04a3bf5e567ed2ecf959cc95e0bdbed179ed632593ea3f85f19c