FAILURE MAP
← Case archive

FA-70136 / Map projection transforms / Open access

Spherical Albers equal-area conic forward: radius division placement · case 01

Parallel spacing is wrong and southern cones fail outright.

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

ROOT CAUSE

The radius multiplies by the cone constant instead of dividing by it.

VERIFIED REPAIR

At the radius division placement step restore `rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n`, leaving the rest of the model unchanged.

Unsuccessful approach: Dividing by |n| makes southern-hemisphere cones project upside down.

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(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 #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('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 #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 #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09])], [('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('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 #17', [-37.667, -43.269, -60.0, -32.0, -5.0, -42.0], [1818158.31, -1416145.5]), ('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])]]
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[942345.16, 5714103.79][2405679.11, -375515.8]Failed
control #1[-1296926.12, -672627.26][-2368752.85, -5460256.95]Failed
control #2[535201.28, -13328739.78][2661851.12, -4920407.16]Failed
control #3[86025.13, -11927373.85][601606.86, -1530950.07]Failed
control #4[-1429446.24, 2879868.93][-3459655.33, -3813601.11]Failed
control #5[249522.55, -1609105.66][499045.1, 3152788.69]Failed
control #6[-110328.33, 7098409.43][-331403.06, 2390895.63]Failed
control #7[-881486.22, 6345098.4][-2250314.51, 1235327.67]Failed

SHA-256 / 2a41b37fde2f424892469d114ba1f1c0573a4c39daad452093d62712ffc12d86

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)) / abs(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 #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('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 #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 #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09])], [('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('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 #17', [-37.667, -43.269, -60.0, -32.0, -5.0, -42.0], [1818158.31, -1416145.5]), ('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])]]
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, -25969222.25][2661851.12, -4920407.16]Failed
control #3[-601606.86, -25793096.84][601606.86, -1530950.07]Failed
control #4[-3459655.33, -3813601.11][-3459655.33, -3813601.11]Passed
control #5[-499045.1, -15894788.69][499045.1, 3152788.69]Failed
control #6[-331403.06, 2390895.63][-331403.06, 2390895.63]Passed
control #7[-2250314.51, 1235327.67][-2250314.51, 1235327.67]Passed

SHA-256 / b3bdf14c1c6416a23611e65c972a8376701029ef2196f723e3fa430b3a08e0a9

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 #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('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 #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 #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('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 #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 #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09])], [('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('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 #17', [-37.667, -43.269, -60.0, -32.0, -5.0, -42.0], [1818158.31, -1416145.5]), ('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])]]
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 #7[-2250314.51, 1235327.67][-2250314.51, 1235327.67]Passed

SHA-256 / bc6819c013e0d44873f34f107fb15c11c6055641ac4abcf911a2c57280420dbe

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

Case digest / 9c17cccf954b5737299c8a1b6df73d83020d8c206a9f6c092c4eeafb67227fe4