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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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