FA-70236 / Map projection transforms / Open access
Mollweide forward with Newton solve: iteration budget · case 01
High-latitude points are misplaced by kilometres.
ROOT CAUSE
The Newton loop is capped at 3 steps, far too few near the poles where the derivative vanishes.
VERIFIED REPAIR
At the iteration budget step restore `for _ in range(50):`, leaving the rest of the model unchanged.
Unsuccessful approach: Raising the cap to 5 still leaves polar latitudes unconverged.
Case contract
Input [lon, lat, lon0]; sphere R = 6371000, dlon wrapped to [-180, 180). Solve 2t + sin 2t = pi sin(phi) by Newton from t = phi, at most 50 steps, stopping when the residual is below 1e-12, with derivative 2 + 2 cos 2t. x = R (2 sqrt 2 / pi) dlon cos t, y = R sqrt 2 sin t, rounded to 2 decimals.
Why this case matters
Global thematic maps use Mollweide for equal area; an under-converged auxiliary angle skews high latitudes.
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 = x
R = 6371000.0
phi = math.radians(lat)
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = phi
for _ in range(3):
f = 2 * theta + math.sin(2 * theta) - math.pi * math.sin(phi)
if abs(f) < 1e-12:
break
theta -= f / (2 + 2 * math.cos(2 * theta))
px = R * 2 * math.sqrt(2) / math.pi * dl * math.cos(theta)
py = R * math.sqrt(2) * math.sin(theta)
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [101.361, 11.005, 160.0], [-5803523.67, 1355983.62]), ('control #1', [-137.531, 16.006, 160.0], [6102963.12, 1966970.83]), ('control #2', [152.471, -58.088, -90.0], [-7888806.65, -6684729.63]), ('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #8', [-149.861, -30.081, 160.0], [4589310.14, -3649245.65]), ('control #9', [101.273, 37.68, 160.0], [-5084573.46, 4523399.69]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #12', [-27.35, -61.095, 10.0], [-2367959.82, -6972924.49]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('control #16', [120.053, 58.23, -90.0], [-10039242.4, 6698546.76]), ('control #17', [164.893, -9.572, 10.0], [15372850.89, -1180095.58]), ('control #18', [35.53, 12.622, 10.0], [2517518.51, 1554061.2]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])]]
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 | [-5803523.67, 1355983.62] | [-5803523.67, 1355983.62] | Passed |
| control #1 | [6102963.12, 1966970.83] | [6102963.12, 1966970.83] | Passed |
| control #2 | [-7888882.78, -6684676.95] | [-7888806.65, -6684729.63] | Failed |
| control #3 | [-4994777.48, -6673669.09] | [-4994731.15, -6673720.01] | Failed |
| control #4 | [3660394.7, 8138977.58] | [3649809.48, 8144275.14] | Failed |
| control #5 | [10594134.8, 6828427.27] | [10593963.16, 6828509.25] | Failed |
| control #7 | [-15037610.8, 2265254.5] | [-15037610.8, 2265254.5] | Passed |
| regression #24 | [-3549009.86, -8754708.02] | [-3106098.59, -8815103.89] | Failed |
SHA-256 / f32a1a68834baac334d87eb6f6b605c3a23ad59d43bed2fcc614d5780e4c059b
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 = x
R = 6371000.0
phi = math.radians(lat)
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = phi
for _ in range(5):
f = 2 * theta + math.sin(2 * theta) - math.pi * math.sin(phi)
if abs(f) < 1e-12:
break
theta -= f / (2 + 2 * math.cos(2 * theta))
px = R * 2 * math.sqrt(2) / math.pi * dl * math.cos(theta)
py = R * math.sqrt(2) * math.sin(theta)
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [101.361, 11.005, 160.0], [-5803523.67, 1355983.62]), ('control #1', [-137.531, 16.006, 160.0], [6102963.12, 1966970.83]), ('control #2', [152.471, -58.088, -90.0], [-7888806.65, -6684729.63]), ('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #8', [-149.861, -30.081, 160.0], [4589310.14, -3649245.65]), ('control #9', [101.273, 37.68, 160.0], [-5084573.46, 4523399.69]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #12', [-27.35, -61.095, 10.0], [-2367959.82, -6972924.49]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('control #16', [120.053, 58.23, -90.0], [-10039242.4, 6698546.76]), ('control #17', [164.893, -9.572, 10.0], [15372850.89, -1180095.58]), ('control #18', [35.53, 12.622, 10.0], [2517518.51, 1554061.2]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])]]
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 | [-5803523.67, 1355983.62] | [-5803523.67, 1355983.62] | Passed |
| control #1 | [6102963.12, 1966970.83] | [6102963.12, 1966970.83] | Passed |
| control #2 | [-7888806.65, -6684729.63] | [-7888806.65, -6684729.63] | Passed |
| control #3 | [-4994731.15, -6673720.01] | [-4994731.15, -6673720.01] | Passed |
| control #4 | [3649809.48, 8144275.14] | [3649809.48, 8144275.14] | Passed |
| control #5 | [10593963.16, 6828509.25] | [10593963.16, 6828509.25] | Passed |
| control #7 | [-15037610.8, 2265254.5] | [-15037610.8, 2265254.5] | Passed |
| regression #24 | [-3106985.44, -8814991.37] | [-3106098.59, -8815103.89] | Failed |
SHA-256 / 657c2c618ef51f3d9da5a248aaedda5a5b2d5e45d3b25f6d52ea3d3ab864bf33
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 = x
R = 6371000.0
phi = math.radians(lat)
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = phi
for _ in range(50):
f = 2 * theta + math.sin(2 * theta) - math.pi * math.sin(phi)
if abs(f) < 1e-12:
break
theta -= f / (2 + 2 * math.cos(2 * theta))
px = R * 2 * math.sqrt(2) / math.pi * dl * math.cos(theta)
py = R * math.sqrt(2) * math.sin(theta)
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [101.361, 11.005, 160.0], [-5803523.67, 1355983.62]), ('control #1', [-137.531, 16.006, 160.0], [6102963.12, 1966970.83]), ('control #2', [152.471, -58.088, -90.0], [-7888806.65, -6684729.63]), ('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #3', [-64.263, -57.975, 10.0], [-4994731.15, -6673720.01]), ('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #4', [95.241, 74.746, 10.0], [3649809.48, 8144275.14]), ('control #7', [114.805, 18.464, -90.0], [-15037610.8, 2265254.5]), ('control #8', [-149.861, -30.081, 160.0], [4589310.14, -3649245.65]), ('control #9', [101.273, 37.68, 160.0], [-5084573.46, 4523399.69]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #5', [172.208, 59.576, 10.0], [10593963.16, 6828509.25]), ('control #10', [-134.836, -65.77, 160.0], [3720190.56, -7401319.17]), ('control #11', [148.892, -11.799, 0.0], [14710485.98, -1453302.05]), ('control #12', [-27.35, -61.095, 10.0], [-2367959.82, -6972924.49]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])], [('control #6', [-110.318, -70.158, 0.0], [-5572506.91, -7778914.97]), ('control #13', [-49.492, 24.945, 0.0], [-4663467.36, 3043363.54]), ('control #14', [165.148, 70.359, 10.0], [7787627.95, 7795585.92]), ('control #15', [-71.762, 5.799, 160.0], [12797412.04, 715745.6]), ('control #16', [120.053, 58.23, -90.0], [-10039242.4, 6698546.76]), ('control #17', [164.893, -9.572, 10.0], [15372850.89, -1180095.58]), ('control #18', [35.53, 12.622, 10.0], [2517518.51, 1554061.2]), ('regression #24', [-150.0, -85.0, 0.0], [-3106098.59, -8815103.89])]]
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 | [-5803523.67, 1355983.62] | [-5803523.67, 1355983.62] | Passed |
| control #1 | [6102963.12, 1966970.83] | [6102963.12, 1966970.83] | Passed |
| control #2 | [-7888806.65, -6684729.63] | [-7888806.65, -6684729.63] | Passed |
| control #3 | [-4994731.15, -6673720.01] | [-4994731.15, -6673720.01] | Passed |
| control #4 | [3649809.48, 8144275.14] | [3649809.48, 8144275.14] | Passed |
| control #5 | [10593963.16, 6828509.25] | [10593963.16, 6828509.25] | Passed |
| control #7 | [-15037610.8, 2265254.5] | [-15037610.8, 2265254.5] | Passed |
| regression #24 | [-3106098.59, -8815103.89] | [-3106098.59, -8815103.89] | Passed |
SHA-256 / 3a5b31e30858bef2979a674d8bd6687c7ba4343f3e8abdf669ed026882361470
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:18.826935+00:00.
Case digest / d71ff3b641b000ecf5367f195be3debfce326d7f0f180ab4fd873f563a356db4