FAILURE MAP
← Case archive

FA-70141 / Map projection transforms / Open access

Spherical Albers equal-area conic forward: origin radius latitude · case 01

False northings are measured from a standard parallel instead of the grid origin.

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

ROOT CAUSE

The origin radius rho0 is evaluated at lat1 instead of lat0.

VERIFIED REPAIR

At the origin radius latitude step restore `math.sqrt(C - 2 * n * math.sin(p0))`, leaving the rest of the model unchanged.

Unsuccessful approach: Switching to lat2 is just as wrong unless the origin lies on that parallel.

Case contract

Input [lon, lat, lon0, lat0, lat1, lat2] degrees, sphere R = 6371000, lat1 != -lat2. n = (sin p1 + sin p2)/2; C = cos(p1)**2 + 2*n*sin(p1); rho = R*sqrt(C - 2*n*sin(phi))/n; rho0 likewise at lat0; theta = n*dlon with dlon wrapped to [-180, 180). x = rho*sin(theta), y = rho0 - rho*cos(theta), rounded to 2 decimals.

Why this case matters

Continental statistics maps use Albers because it preserves area; a wrong constant silently biases area totals.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    lon, lat, lon0, lat0, lat1, lat2 = x
    R = 6371000.0
    p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
    n = (math.sin(p1) + math.sin(p2)) / 2
    C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
    rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
    rho0 = R * math.sqrt(C - 2 * n * math.sin(p1)) / n
    dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
    theta = n * dl
    return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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
control #0[2405679.11, -1474011.34][2405679.11, -375515.8]Failed
control #1[-2368752.85, -3527724.08][-2368752.85, -5460256.95]Failed
control #2[2661851.12, -2988451.25][2661851.12, -4920407.16]Failed
control #3[601606.86, -4652930.26][601606.86, -1530950.07]Failed
control #4[-3459655.33, -3813601.11][-3459655.33, -3813601.11]Passed
control #5[499045.1, 3152788.69][499045.1, 3152788.69]Passed
control #6[-331403.06, 2950506.66][-331403.06, 2390895.63]Failed
control #11[1977746.83, 796959.72][1977746.83, 796959.72]Passed

SHA-256 / 62e3635e311a2c53e60e7889256773a63fda9ba6a57dbb282246403cd0fe51a9

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    lon, lat, lon0, lat0, lat1, lat2 = x
    R = 6371000.0
    p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
    n = (math.sin(p1) + math.sin(p2)) / 2
    C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
    rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
    rho0 = R * math.sqrt(C - 2 * n * math.sin(p2)) / n
    dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
    theta = n * dl
    return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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
control #0[2405679.11, -2813249.5][2405679.11, -375515.8]Failed
control #1[-2368752.85, -6941932.69][-2368752.85, -5460256.95]Failed
control #2[2661851.12, -970316.72][2661851.12, -4920407.16]Failed
control #3[601606.86, -389516.7][601606.86, -1530950.07]Failed
control #4[-3459655.33, -3813601.11][-3459655.33, -3813601.11]Passed
control #5[499045.1, 3152788.69][499045.1, 3152788.69]Passed
control #6[-331403.06, 473712.76][-331403.06, 2390895.63]Failed
control #11[1977746.83, 796959.72][1977746.83, 796959.72]Passed

SHA-256 / 343e5da0141de70f361ca311bc1eefbd55369afaf606dfd3ec15aebf798cb425

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    lon, lat, lon0, lat0, lat1, lat2 = x
    R = 6371000.0
    p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
    n = (math.sin(p1) + math.sin(p2)) / 2
    C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
    rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
    rho0 = R * math.sqrt(C - 2 * n * math.sin(p0)) / n
    dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
    theta = n * dl
    return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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
control #0[2405679.11, -375515.8][2405679.11, -375515.8]Passed
control #1[-2368752.85, -5460256.95][-2368752.85, -5460256.95]Passed
control #2[2661851.12, -4920407.16][2661851.12, -4920407.16]Passed
control #3[601606.86, -1530950.07][601606.86, -1530950.07]Passed
control #4[-3459655.33, -3813601.11][-3459655.33, -3813601.11]Passed
control #5[499045.1, 3152788.69][499045.1, 3152788.69]Passed
control #6[-331403.06, 2390895.63][-331403.06, 2390895.63]Passed
control #11[1977746.83, 796959.72][1977746.83, 796959.72]Passed

SHA-256 / e1263e8da38727005b86e92efdde16005a76ca52247c1fee53fda4766df1cfaf

Verification & scope

Stipulated deterministic toy contract on a bounded input domain; results are rounded as stated and no conformance with any published standard or library is claimed. 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:48:17.828580+00:00.

Case digest / 25217cb081cd2d91e261c55bf51bbd41aa80760d08a74277b088f6268391fd0d