LJdacnMXkr / evidence /code /resolution.py
DineshAI's picture
Publish cumulative Sinkhorn reproduction evidence
5338e3e verified
Raw
History Blame Contribute Delete
17.5 kB
"""Direct numerical contract for the resolution limit in Theorem 4.2.
This module uses only deterministic quadrature. The discrete measures are
positive midpoint rules that weakly converge to a positive continuous measure
on [0, 1]. The continuous reference is computed independently with
Gauss--Legendre quadrature at two orders.
"""
from __future__ import annotations
import csv
import hashlib
import json
import os
import platform
import subprocess
import sys
import time
from pathlib import Path
import numpy as np
from numpy.polynomial.legendre import leggauss
from scipy.spatial.distance import cdist
ROOT = Path(__file__).resolve().parents[2]
ARTIFACT_DIR = ROOT / ".openresearch" / "artifacts" / "claim_2"
FIXED_COMMAND = "uv run python repro/src/verify.py"
def _kernel(left: np.ndarray, right: np.ndarray, name: str, sigma: float) -> np.ndarray:
distances = cdist(left[:, None], right[:, None], metric="euclidean")
if name == "gaussian":
return np.exp(-(distances**2) / (2.0 * sigma**2))
if name == "exponential":
return np.exp(-distances / sigma)
raise ValueError(f"unsupported kernel: {name}")
def _density(points: np.ndarray, name: str) -> np.ndarray:
if name == "uniform":
return np.ones_like(points)
if name == "affine":
# Strictly positive on the closed domain and integrates to one.
return 0.5 + points
if name == "oscillatory":
# Strict lower bound 0.55; integral is one.
return 1.0 + 0.45 * np.cos(2.0 * np.pi * points)
raise ValueError(f"unsupported density: {name}")
def _signal(points: np.ndarray, name: str) -> np.ndarray:
if name == "constant":
return np.ones_like(points)
if name == "linear":
return points
if name == "quadratic":
return points**2
if name == "sin_2pi":
return np.sin(2.0 * np.pi * points)
if name == "cos_3pi":
return np.cos(3.0 * np.pi * points)
raise ValueError(f"unsupported signal: {name}")
def _sinkhorn_scaling(
points: np.ndarray,
weights: np.ndarray,
kernel_name: str,
sigma: float,
tolerance: float = 2e-14,
max_iterations: int = 2_000,
) -> tuple[np.ndarray, int, float]:
kernel = _kernel(points, points, kernel_name, sigma)
scaling = np.ones(points.size, dtype=np.float64)
residual = float("inf")
for iteration in range(1, max_iterations + 1):
denominator = kernel @ (weights * scaling)
scaling = np.sqrt(scaling / np.maximum(denominator, 1e-300))
residual = float(
np.max(np.abs(scaling * (kernel @ (weights * scaling)) - 1.0))
)
if residual < tolerance:
return scaling, iteration, residual
raise RuntimeError(
f"Sinkhorn failed: kernel={kernel_name} n={points.size} residual={residual}"
)
def _operator_on_probes(
nodes: np.ndarray,
weights: np.ndarray,
scaling: np.ndarray,
probes: np.ndarray,
signal_name: str,
kernel_name: str,
sigma: float,
) -> tuple[np.ndarray, float]:
cross_kernel = _kernel(probes, nodes, kernel_name, sigma)
probe_denominator = cross_kernel @ (weights * scaling)
probe_scaling = 1.0 / np.maximum(probe_denominator, 1e-300)
values = probe_scaling * (
cross_kernel @ (weights * scaling * _signal(nodes, signal_name))
)
constant_residual = float(
np.max(
np.abs(
probe_scaling * (cross_kernel @ (weights * scaling))
- np.ones(probes.size)
)
)
)
return values, constant_residual
def _midpoint_measure(n: int, density_name: str) -> tuple[np.ndarray, np.ndarray]:
nodes = (np.arange(n, dtype=np.float64) + 0.5) / n
weights = _density(nodes, density_name) / n
# Normalization removes only finite quadrature error, preserves positivity,
# and does not affect weak convergence.
weights /= weights.sum()
return nodes, weights
def _quantile_measure(n: int, density_name: str) -> tuple[np.ndarray, np.ndarray]:
probabilities = (np.arange(n, dtype=np.float64) + 0.5) / n
if density_name == "uniform":
nodes = probabilities
elif density_name == "affine":
# Invert F(x) = (x + x^2) / 2.
nodes = 0.5 * (-1.0 + np.sqrt(1.0 + 8.0 * probabilities))
elif density_name == "oscillatory":
# Invert F(x) = x + 0.45 sin(2 pi x)/(2 pi) by bisection.
lower = np.zeros(n, dtype=np.float64)
upper = np.ones(n, dtype=np.float64)
for _ in range(60):
middle = 0.5 * (lower + upper)
cdf = middle + 0.45 * np.sin(2.0 * np.pi * middle) / (
2.0 * np.pi
)
lower = np.where(cdf < probabilities, middle, lower)
upper = np.where(cdf >= probabilities, middle, upper)
nodes = 0.5 * (lower + upper)
else:
raise ValueError(f"unsupported density: {density_name}")
return nodes, np.full(n, 1.0 / n, dtype=np.float64)
def _discrete_measure(
n: int, density_name: str, discretization: str
) -> tuple[np.ndarray, np.ndarray]:
if discretization == "midpoint":
return _midpoint_measure(n, density_name)
if discretization == "quantile":
return _quantile_measure(n, density_name)
raise ValueError(f"unsupported discretization: {discretization}")
def _gauss_measure(order: int, density_name: str) -> tuple[np.ndarray, np.ndarray]:
canonical_nodes, canonical_weights = leggauss(order)
nodes = 0.5 * (canonical_nodes + 1.0)
weights = 0.5 * canonical_weights * _density(nodes, density_name)
weights /= weights.sum()
return nodes, weights
def _fit_slope(resolutions: list[int], errors: list[float]) -> float:
safe = np.maximum(np.asarray(errors, dtype=np.float64), 1e-18)
return float(np.polyfit(np.log(np.asarray(resolutions)), np.log(safe), 1)[0])
def _reference_outputs(
order: int,
density_name: str,
kernel_name: str,
sigma: float,
signals: list[str],
probes: np.ndarray,
) -> tuple[dict[str, np.ndarray], dict[str, float], int, float]:
nodes, weights = _gauss_measure(order, density_name)
scaling, iterations, residual = _sinkhorn_scaling(
nodes, weights, kernel_name, sigma
)
values: dict[str, np.ndarray] = {}
constant_residuals: dict[str, float] = {}
for signal_name in signals:
values[signal_name], constant_residuals[signal_name] = _operator_on_probes(
nodes,
weights,
scaling,
probes,
signal_name,
kernel_name,
sigma,
)
return values, constant_residuals, iterations, residual
def _git_sha() -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def run_resolution_contract(config_path: Path | None = None) -> dict:
start = time.perf_counter()
if config_path is None:
config_path = ROOT / "repro" / "config.json"
config = json.loads(config_path.read_text(encoding="utf-8"))
probes = np.linspace(0.0, 1.0, int(config["probe_count"]))
resolutions = [int(value) for value in config["resolutions"]]
reference_orders = [int(value) for value in config["reference_orders"]]
signals = [str(value) for value in config["signals"]]
cases: list[dict] = []
for density_name in config["densities"]:
for discretization in config["discretizations"]:
for kernel_spec in config["kernels"]:
kernel_name = str(kernel_spec["name"])
sigma = float(kernel_spec["sigma"])
coarse_reference, coarse_constant, coarse_iterations, coarse_residual = (
_reference_outputs(
reference_orders[0],
density_name,
kernel_name,
sigma,
signals,
probes,
)
)
fine_reference, fine_constant, fine_iterations, fine_residual = (
_reference_outputs(
reference_orders[1],
density_name,
kernel_name,
sigma,
signals,
probes,
)
)
reference_stability = {
signal_name: float(
np.max(
np.abs(
coarse_reference[signal_name]
- fine_reference[signal_name]
)
)
)
for signal_name in signals
}
error_curves = {signal_name: [] for signal_name in signals}
constant_residuals: list[float] = []
scaling_records: list[dict] = []
for n in resolutions:
nodes, weights = _discrete_measure(
n, density_name, discretization
)
scaling, iterations, residual = _sinkhorn_scaling(
nodes, weights, kernel_name, sigma
)
scaling_records.append(
{
"n": n,
"iterations": iterations,
"node_residual": residual,
"min_weight": float(weights.min()),
"min_scaling": float(scaling.min()),
}
)
for signal_name in signals:
values, constant_residual = _operator_on_probes(
nodes,
weights,
scaling,
probes,
signal_name,
kernel_name,
sigma,
)
error_curves[signal_name].append(
float(
np.max(
np.abs(
values - fine_reference[signal_name]
)
)
)
)
constant_residuals.append(constant_residual)
signal_records = []
for signal_name in signals:
errors = error_curves[signal_name]
signal_records.append(
{
"signal": signal_name,
"uniform_errors": errors,
"final_error": errors[-1],
"reduction_ratio": errors[-1]
/ max(errors[0], 1e-300),
"loglog_slope": _fit_slope(resolutions, errors),
"reference_stability": reference_stability[
signal_name
],
}
)
cases.append(
{
"density": density_name,
"discretization": discretization,
"kernel": kernel_name,
"sigma": sigma,
"reference": {
"orders": reference_orders,
"coarse_iterations": coarse_iterations,
"fine_iterations": fine_iterations,
"coarse_node_residual": coarse_residual,
"fine_node_residual": fine_residual,
"max_constant_residual": max(
list(coarse_constant.values())
+ list(fine_constant.values())
),
},
"scalings": scaling_records,
"max_probe_constant_residual": max(
constant_residuals
),
"signals": signal_records,
}
)
# This deliberately violates resolution increase: the same n=32 result is
# repeated at every nominal t. It must not satisfy the convergence gates.
negative_cases = []
for case in cases:
negative_signals = []
for record in case["signals"]:
repeated = [record["uniform_errors"][0]] * len(resolutions)
negative_signals.append(
{
"signal": record["signal"],
"uniform_errors": repeated,
"final_error": repeated[-1],
"reduction_ratio": 1.0,
"loglog_slope": _fit_slope(resolutions, repeated),
"reference_stability": record["reference_stability"],
}
)
negative_cases.append(
{
"density": case["density"],
"discretization": case["discretization"],
"kernel": case["kernel"],
"sigma": case["sigma"],
"reference": case["reference"],
"max_probe_constant_residual": case[
"max_probe_constant_residual"
],
"signals": negative_signals,
}
)
result = {
"schema_version": 1,
"claim": "Theorem 4.2 resolution convergence",
"variant": config["resolution_variant"],
"domain": "[0,1]",
"resolutions": resolutions,
"probe_count": int(config["probe_count"]),
"fixed_command": FIXED_COMMAND,
"seed": int(config["seed"]),
"cases": cases,
"negative_control": {
"name": "fixed_resolution_relabelled_as_increasing",
"expected_to_pass": False,
"cases": negative_cases,
},
"runtime_seconds": time.perf_counter() - start,
}
return result
def write_artifacts(result: dict) -> None:
ARTIFACT_DIR.mkdir(parents=True, exist_ok=True)
raw_json = ARTIFACT_DIR / "raw_results.json"
raw_json.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
with (ARTIFACT_DIR / "raw_results.csv").open(
"w", newline="", encoding="utf-8"
) as handle:
writer = csv.DictWriter(
handle,
fieldnames=[
"variant",
"density",
"discretization",
"kernel",
"sigma",
"signal",
"n",
"uniform_error",
],
)
writer.writeheader()
for case in result["cases"]:
for record in case["signals"]:
for n, error in zip(result["resolutions"], record["uniform_errors"]):
writer.writerow(
{
"variant": result["variant"],
"density": case["density"],
"discretization": case["discretization"],
"kernel": case["kernel"],
"sigma": case["sigma"],
"signal": record["signal"],
"n": n,
"uniform_error": f"{error:.17g}",
}
)
environment = {
"git_sha": _git_sha(),
"fixed_command": FIXED_COMMAND,
"python": sys.version,
"platform": platform.platform(),
"processor": platform.processor(),
"logical_cpu_count": os.cpu_count(),
"numpy": np.__version__,
"config_sha256": _sha256(ROOT / "repro" / "config.json"),
"lock_sha256": _sha256(ROOT / "uv.lock"),
"seed": result["seed"],
"runtime_seconds": result["runtime_seconds"],
}
(ARTIFACT_DIR / "environment.json").write_text(
json.dumps(environment, indent=2) + "\n", encoding="utf-8"
)
def print_summary(result: dict) -> None:
print("C2_RESOLUTION_VARIANT=" + str(result["variant"]))
print("C2_FIXED_COMMAND=" + FIXED_COMMAND)
print("C2_RESOLUTIONS=" + json.dumps(result["resolutions"]))
for case in result["cases"]:
for record in case["signals"]:
print(
"C2_RESULT "
f"density={case['density']} discretization={case['discretization']} "
f"kernel={case['kernel']} "
f"sigma={case['sigma']:.6g} signal={record['signal']} "
f"errors={json.dumps(record['uniform_errors'])} "
f"ratio={record['reduction_ratio']:.8g} "
f"slope={record['loglog_slope']:.8g} "
f"ref_stability={record['reference_stability']:.8g}"
)
print(f"C2_RUNTIME_SECONDS={result['runtime_seconds']:.6f}")