FAILURE MAP
← Case archive

FA-12181 / Astronomical coordinate conventions / Open access

SIP distortion is evaluated after the linear WCS transform · case 01

SIP distortion is evaluated after the linear WCS transform.

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

ROOT CAUSE

Pixel-space distortion coefficients are evaluated in intermediate world coordinates.

VERIFIED REPAIR

Evaluate the stipulated SIP polynomial in pixel offsets before applying the linear matrix.

Unsuccessful approach: Moving only the square term before the matrix leaves the mixed term in the wrong basis.

Case contract

Reduced SIP model for reference-relative pixel offsets u,v: corrected pair (u+a*u*u+b*u*v,v), followed by diagonal linear matrix (sx,sy). Return intermediate coordinates, no sky projection or full SIP implementation.

Why this case matters

Astronomical coordinate adapters must preserve convention semantics. Primary background: https://docs.astropy.org/en/stable/api/astropy.wcs.WCS.html

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(u, v, a, b, sx, sy):
    return [sx*u+a*(sx*u)**2+b*(sx*u)*(sy*v),sy*v]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('square and cross distortion', solve(N,2,1,1,3,4), [3*(N+N*N+2*N),8])
check('cross term only', solve(N,2,0,1,3,4), [9*N,8])
check('square only', solve(N,0,1,0,3,4), [3*(N+N*N),0])
check('reference origin', solve(0,0,1,1,3,4), [0,0])
check('identity transform', solve(N,2,1,1,1,1), [N+N*N+2*N,2])
check('no distortion', solve(N,-2,0,0,3,4), [3*N,-8])
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
square and cross distortion[36, 8][12, 8]Failed
cross term only[27, 8][9, 8]Failed
square only[12, 0][6, 0]Failed
reference origin[0, 0][0, 0]Passed
identity transform[4, 2][4, 2]Passed
no distortion[3, -8][3, -8]Passed

SHA-256 / a0736d8663079cee35c466074501ecd4c8f7874c59784d50e6aed3520fc84570

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(u, v, a, b, sx, sy):
    return [sx*(u+a*u*u)+b*(sx*u)*(sy*v),sy*v]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('square and cross distortion', solve(N,2,1,1,3,4), [3*(N+N*N+2*N),8])
check('cross term only', solve(N,2,0,1,3,4), [9*N,8])
check('square only', solve(N,0,1,0,3,4), [3*(N+N*N),0])
check('reference origin', solve(0,0,1,1,3,4), [0,0])
check('identity transform', solve(N,2,1,1,1,1), [N+N*N+2*N,2])
check('no distortion', solve(N,-2,0,0,3,4), [3*N,-8])
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
square and cross distortion[30, 8][12, 8]Failed
cross term only[27, 8][9, 8]Failed
square only[6, 0][6, 0]Passed
reference origin[0, 0][0, 0]Passed
identity transform[4, 2][4, 2]Passed
no distortion[3, -8][3, -8]Passed

SHA-256 / 9d885096e1f3b9338b125316a6e6d67e846053617dc15e7c94bb41d990f4b28d

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(u, v, a, b, sx, sy):
    return [sx*(u+a*u*u+b*u*v),sy*v]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('square and cross distortion', solve(N,2,1,1,3,4), [3*(N+N*N+2*N),8])
check('cross term only', solve(N,2,0,1,3,4), [9*N,8])
check('square only', solve(N,0,1,0,3,4), [3*(N+N*N),0])
check('reference origin', solve(0,0,1,1,3,4), [0,0])
check('identity transform', solve(N,2,1,1,1,1), [N+N*N+2*N,2])
check('no distortion', solve(N,-2,0,0,3,4), [3*N,-8])
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
square and cross distortion[12, 8][12, 8]Passed
cross term only[9, 8][9, 8]Passed
square only[6, 0][6, 0]Passed
reference origin[0, 0][0, 0]Passed
identity transform[4, 2][4, 2]Passed
no distortion[3, -8][3, -8]Passed

SHA-256 / 0d9eac558fd64ba6851da847f12b4a69172e561f464af2df741a31851e02a797

Verification & scope

Reduced deterministic algebraic model only; no ephemeris, full sky projection, or physical accuracy claims. 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:38:54.579426+00:00.

Case digest / 7d0692112b37736a3674861465775ba1b9c484dc34b15565bb7dbb27ca0202c1