FAILURE MAP
← Case archive

FA-71636 / Seismic magnitude estimation / Open access

GNSS peak ground displacement magnitude: magnitude inversion · case 01

PGD magnitudes are several units too high and distance-independent.

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

ROOT CAUSE

Operator precedence drops the parentheses around B + C log10 R.

VERIFIED REPAIR

Divide by (B + C log10 R).

Unsuccessful approach: Adding A instead of subtracting it shifts every magnitude.

Case contract

obs are [PGD in m, epicentral km]. Per station PGD in cm, R = hypocentral distance; skip PGD < 2 cm or R > 1000 km. log10(PGD) = A + B M + C M log10 R with A=-4.434, B=1.047, C=-0.138, solved for M. Return [mean M rounded 0.01, stations used] or None.

Why this case matters

GNSS PGD magnitudes do not saturate for great earthquakes in early warning.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(obs, depth_km):
    A, B, C = -4.434, 1.047, -0.138
    ms = []
    for pgd_m, epi in obs:
        pgd_cm = pgd_m * 100.0
        r = math.hypot(epi, depth_km)
        if pgd_cm < 2.0 or r > 1000:
            continue
        ms.append((math.log10(pgd_cm) - A) / B + C * math.log10(r))
    if not ms:
        return None
    return [round(sum(ms) / len(ms), 2), len(ms)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['GNSS set 0', [[[0.005, 700], [0.05, 1100], [0.02, 995], [0.02, 700]], 25], [7.36, 2]], ['GNSS set 1', [[[0.005, 200], [0.005, 10], [1.2, 995], [0.005, 995], [0.02, 995]], 10], [8.88, 2]], ['GNSS set 2', [[[1.2, 200]], 10], [8.93, 1]], ['GNSS set 3', [[[1.2, 1100]], 10], None], ['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 5', [[[0.005, 10], [0.3, 60], [0.3, 1100], [0.3, 10]], 60], [7.48, 2]], ['GNSS set 6', [[[0.3, 10], [0.005, 200], [1.2, 1100], [0.005, 1100], [0.02, 60]], 60], [6.72, 2]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None]], [['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None], ['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 9', [[[1.2, 60], [0.3, 10], [0.05, 60]], 25], [7.19, 3]], ['GNSS set 10', [[[0.3, 700], [0.02, 1100]], 60], [9.04, 1]], ['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 12', [[[0.3, 200]], 25], [8.11, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]]], [['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]], ['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 15', [[[0.005, 60]], 10], None], ['GNSS set 16', [[[1.2, 200], [0.005, 200], [0.02, 10], [0.3, 700]], 25], [7.85, 3]], ['GNSS set 17', [[[0.3, 200], [0.05, 995]], 60], [8.12, 2]], ['GNSS set 18', [[[0.02, 10], [0.3, 1100], [1.2, 200], [0.005, 700], [1.2, 995]], 10], [8.18, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]]], [['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]], ['GNSS set 21', [[[1.2, 200], [0.02, 1100], [0.005, 700], [0.3, 995]], 10], [9.13, 2]], ['GNSS set 22', [[[0.05, 700], [1.2, 200], [1.2, 10], [1.2, 1100], [0.3, 1100]], 10], [8.04, 3]], ['GNSS set 23', [[[0.3, 700], [0.05, 995], [1.2, 200], [0.005, 10]], 25], [8.69, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 25', [[[0.005, 1100], [0.3, 60], [1.2, 10], [0.05, 700], [1.2, 60]], 25], [7.78, 4]], ['GNSS set 26', [[[0.05, 995]], 60], [8.11, 1]]], [['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 28', [[[0.02, 1100], [0.005, 200], [0.05, 200]], 10], [7.04, 1]], ['GNSS set 29', [[[0.3, 60], [0.02, 1100], [0.3, 995], [1.2, 60]], 60], [8.42, 3]], ['GNSS set 30', [[[0.3, 200], [1.2, 10], [0.3, 1100]], 60], [8.13, 2]], ['GNSS set 31', [[[0.05, 200], [0.05, 700]], 10], [7.44, 2]], ['GNSS set 32', [[[1.2, 1100]], 10], None], ['GNSS set 33', [[[0.02, 10], [0.3, 995], [1.2, 1100], [0.02, 700]], 60], [7.5, 3]]]]
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
GNSS set 0[4.12, 2][7.36, 2]Failed
GNSS set 1[4.96, 2][8.88, 2]Failed
GNSS set 2[5.9, 1][8.93, 1]Failed
GNSS set 3NoneNonePassed
GNSS set 4[5.33, 3][8.3, 3]Failed
GNSS set 5[5.39, 2][7.48, 2]Failed
GNSS set 6[4.83, 2][6.72, 2]Failed
GNSS set 7NoneNonePassed

SHA-256 / 45340db8101377d39fde2065ced4b157d7a44c378b0efbc262f4fd596a45a9e3

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(obs, depth_km):
    A, B, C = -4.434, 1.047, -0.138
    ms = []
    for pgd_m, epi in obs:
        pgd_cm = pgd_m * 100.0
        r = math.hypot(epi, depth_km)
        if pgd_cm < 2.0 or r > 1000:
            continue
        ms.append((math.log10(pgd_cm) + A) / (B + C * math.log10(r)))
    if not ms:
        return None
    return [round(sum(ms) / len(ms), 2), len(ms)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['GNSS set 0', [[[0.005, 700], [0.05, 1100], [0.02, 995], [0.02, 700]], 25], [7.36, 2]], ['GNSS set 1', [[[0.005, 200], [0.005, 10], [1.2, 995], [0.005, 995], [0.02, 995]], 10], [8.88, 2]], ['GNSS set 2', [[[1.2, 200]], 10], [8.93, 1]], ['GNSS set 3', [[[1.2, 1100]], 10], None], ['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 5', [[[0.005, 10], [0.3, 60], [0.3, 1100], [0.3, 10]], 60], [7.48, 2]], ['GNSS set 6', [[[0.3, 10], [0.005, 200], [1.2, 1100], [0.005, 1100], [0.02, 60]], 60], [6.72, 2]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None]], [['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None], ['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 9', [[[1.2, 60], [0.3, 10], [0.05, 60]], 25], [7.19, 3]], ['GNSS set 10', [[[0.3, 700], [0.02, 1100]], 60], [9.04, 1]], ['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 12', [[[0.3, 200]], 25], [8.11, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]]], [['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]], ['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 15', [[[0.005, 60]], 10], None], ['GNSS set 16', [[[1.2, 200], [0.005, 200], [0.02, 10], [0.3, 700]], 25], [7.85, 3]], ['GNSS set 17', [[[0.3, 200], [0.05, 995]], 60], [8.12, 2]], ['GNSS set 18', [[[0.02, 10], [0.3, 1100], [1.2, 200], [0.005, 700], [1.2, 995]], 10], [8.18, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]]], [['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]], ['GNSS set 21', [[[1.2, 200], [0.02, 1100], [0.005, 700], [0.3, 995]], 10], [9.13, 2]], ['GNSS set 22', [[[0.05, 700], [1.2, 200], [1.2, 10], [1.2, 1100], [0.3, 1100]], 10], [8.04, 3]], ['GNSS set 23', [[[0.3, 700], [0.05, 995], [1.2, 200], [0.005, 10]], 25], [8.69, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 25', [[[0.005, 1100], [0.3, 60], [1.2, 10], [0.05, 700], [1.2, 60]], 25], [7.78, 4]], ['GNSS set 26', [[[0.05, 995]], 60], [8.11, 1]]], [['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 28', [[[0.02, 1100], [0.005, 200], [0.05, 200]], 10], [7.04, 1]], ['GNSS set 29', [[[0.3, 60], [0.02, 1100], [0.3, 995], [1.2, 60]], 60], [8.42, 3]], ['GNSS set 30', [[[0.3, 200], [1.2, 10], [0.3, 1100]], 60], [8.13, 2]], ['GNSS set 31', [[[0.05, 200], [0.05, 700]], 10], [7.44, 2]], ['GNSS set 32', [[[1.2, 1100]], 10], None], ['GNSS set 33', [[[0.02, 10], [0.3, 995], [1.2, 1100], [0.02, 700]], 60], [7.5, 3]]]]
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
GNSS set 0[-6.42, 2][7.36, 2]Failed
GNSS set 1[-5.12, 2][8.88, 2]Failed
GNSS set 2[-3.23, 1][8.93, 1]Failed
GNSS set 3NoneNonePassed
GNSS set 4[-4.11, 3][8.3, 3]Failed
GNSS set 5[-3.74, 2][7.48, 2]Failed
GNSS set 6[-4.49, 2][6.72, 2]Failed
GNSS set 7NoneNonePassed

SHA-256 / f7c27bf9827b0d52b44e4e057c2af27b5b7cd99411e8d2865ba74993c4fba3b7

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(obs, depth_km):
    A, B, C = -4.434, 1.047, -0.138
    ms = []
    for pgd_m, epi in obs:
        pgd_cm = pgd_m * 100.0
        r = math.hypot(epi, depth_km)
        if pgd_cm < 2.0 or r > 1000:
            continue
        ms.append((math.log10(pgd_cm) - A) / (B + C * math.log10(r)))
    if not ms:
        return None
    return [round(sum(ms) / len(ms), 2), len(ms)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['GNSS set 0', [[[0.005, 700], [0.05, 1100], [0.02, 995], [0.02, 700]], 25], [7.36, 2]], ['GNSS set 1', [[[0.005, 200], [0.005, 10], [1.2, 995], [0.005, 995], [0.02, 995]], 10], [8.88, 2]], ['GNSS set 2', [[[1.2, 200]], 10], [8.93, 1]], ['GNSS set 3', [[[1.2, 1100]], 10], None], ['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 5', [[[0.005, 10], [0.3, 60], [0.3, 1100], [0.3, 10]], 60], [7.48, 2]], ['GNSS set 6', [[[0.3, 10], [0.005, 200], [1.2, 1100], [0.005, 1100], [0.02, 60]], 60], [6.72, 2]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None]], [['GNSS set 4', [[[0.005, 995], [0.02, 200], [0.005, 1100], [1.2, 995], [1.2, 60]], 10], [8.3, 3]], ['GNSS set 7', [[[0.005, 700], [1.2, 1100]], 25], None], ['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 9', [[[1.2, 60], [0.3, 10], [0.05, 60]], 25], [7.19, 3]], ['GNSS set 10', [[[0.3, 700], [0.02, 1100]], 60], [9.04, 1]], ['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 12', [[[0.3, 200]], 25], [8.11, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]]], [['GNSS set 8', [[[1.2, 700], [0.005, 10]], 10], [9.95, 1]], ['GNSS set 13', [[[0.005, 700], [0.05, 700], [1.2, 995], [0.3, 700]], 25], [9.05, 3]], ['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 15', [[[0.005, 60]], 10], None], ['GNSS set 16', [[[1.2, 200], [0.005, 200], [0.02, 10], [0.3, 700]], 25], [7.85, 3]], ['GNSS set 17', [[[0.3, 200], [0.05, 995]], 60], [8.12, 2]], ['GNSS set 18', [[[0.02, 10], [0.3, 1100], [1.2, 200], [0.005, 700], [1.2, 995]], 10], [8.18, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]]], [['GNSS set 11', [[[1.2, 1100], [0.02, 200], [0.02, 200], [0.005, 1100], [0.05, 10]], 25], [6.34, 3]], ['GNSS set 19', [[[0.02, 700], [0.02, 60]], 60], [6.65, 2]], ['GNSS set 21', [[[1.2, 200], [0.02, 1100], [0.005, 700], [0.3, 995]], 10], [9.13, 2]], ['GNSS set 22', [[[0.05, 700], [1.2, 200], [1.2, 10], [1.2, 1100], [0.3, 1100]], 10], [8.04, 3]], ['GNSS set 23', [[[0.3, 700], [0.05, 995], [1.2, 200], [0.005, 10]], 25], [8.69, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 25', [[[0.005, 1100], [0.3, 60], [1.2, 10], [0.05, 700], [1.2, 60]], 25], [7.78, 4]], ['GNSS set 26', [[[0.05, 995]], 60], [8.11, 1]]], [['GNSS set 14', [[[1.2, 700], [0.02, 200], [0.02, 700]], 10], [7.89, 3]], ['GNSS set 24', [[[0.02, 1100], [1.2, 200], [0.02, 1100], [0.3, 1100]], 60], [8.96, 1]], ['GNSS set 28', [[[0.02, 1100], [0.005, 200], [0.05, 200]], 10], [7.04, 1]], ['GNSS set 29', [[[0.3, 60], [0.02, 1100], [0.3, 995], [1.2, 60]], 60], [8.42, 3]], ['GNSS set 30', [[[0.3, 200], [1.2, 10], [0.3, 1100]], 60], [8.13, 2]], ['GNSS set 31', [[[0.05, 200], [0.05, 700]], 10], [7.44, 2]], ['GNSS set 32', [[[1.2, 1100]], 10], None], ['GNSS set 33', [[[0.02, 10], [0.3, 995], [1.2, 1100], [0.02, 700]], 60], [7.5, 3]]]]
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
GNSS set 0[7.36, 2][7.36, 2]Passed
GNSS set 1[8.88, 2][8.88, 2]Passed
GNSS set 2[8.93, 1][8.93, 1]Passed
GNSS set 3NoneNonePassed
GNSS set 4[8.3, 3][8.3, 3]Passed
GNSS set 5[7.48, 2][7.48, 2]Passed
GNSS set 6[6.72, 2][6.72, 2]Passed
GNSS set 7NoneNonePassed

SHA-256 / 603be488c8fe5b370d9d8f8850134eb3a5f08c1490d03dd3d257c96cba0550fe

Verification & scope

Stipulated deterministic teaching model of a seismological magnitude procedure; constants and tables are fixed by the contract and no claim of agency or standards conformance is made. 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:31.433336+00:00.

Case digest / 911f8cb072d648344ac54edf432db8d30349d4c0fae84f2fd10d6c454c001dcb