a77edafcbf
Validate Operations / validate-operations (push) Has been cancelled
plot adjustments validation adjustments
45 lines
2.1 KiB
Python
45 lines
2.1 KiB
Python
from dataclasses import dataclass
|
|
import math
|
|
|
|
import numpy as np
|
|
|
|
|
|
VALIDATION_POLICY = "pointwise-and-relative-l2-v1"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OutputComparison:
|
|
passed: bool
|
|
max_abs_diff: float
|
|
relative_l2_error: float | None
|
|
pointwise_passed: bool
|
|
|
|
|
|
def compare_output_arrays(actual, expected, *, atol, rtol):
|
|
"""Require pointwise tolerance and relative L2 tolerance for nonzero outputs."""
|
|
if any(not math.isfinite(value) or value < 0 for value in (atol, rtol)):
|
|
raise ValueError("output tolerances must be finite and non-negative")
|
|
actual, expected = np.asarray(actual), np.asarray(expected)
|
|
if actual.dtype.kind not in "biuf" or expected.dtype.kind not in "biuf":
|
|
raise ValueError("output comparison requires real numeric arrays")
|
|
if actual.shape != expected.shape:
|
|
raise ValueError(f"output shapes differ: {actual.shape} != {expected.shape}")
|
|
if not actual.size:
|
|
return OutputComparison(True, 0.0, None, True)
|
|
if not np.all(np.isfinite(actual)) or not np.all(np.isfinite(expected)):
|
|
return OutputComparison(False, math.inf, None, False)
|
|
if expected.dtype.kind in "biu":
|
|
difference = np.abs(actual.astype(object) - expected.astype(object))
|
|
passed = bool(np.array_equal(actual, expected))
|
|
return OutputComparison(passed, float(difference.max()), None, passed)
|
|
actual, expected = actual.astype(np.float64), expected.astype(np.float64)
|
|
with np.errstate(over="ignore", invalid="ignore", divide="ignore"):
|
|
difference = np.abs(actual - expected)
|
|
pointwise = bool(np.all(difference <= atol + rtol * np.abs(expected)))
|
|
scale = float(np.max(np.abs(expected)))
|
|
relative = (float(np.linalg.norm(difference / scale) /
|
|
np.linalg.norm(expected / scale)) if scale else None)
|
|
# A zero reference has no relative scale. rtol=0 explicitly requests absolute-only checks.
|
|
relative_passed = relative is None or rtol == 0 or relative <= rtol
|
|
return OutputComparison(pointwise and relative_passed, float(difference.max()), relative, pointwise)
|