#!/usr/bin/env python3 """Deterministic audit for OpenReview QYA0Q28ssf.""" from __future__ import annotations import argparse import csv import hashlib import json import math from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np CLAIMS = [ "Under finite drift energy assumptions, the intra-generation KL divergence between the generated distribution and the training target is bounded above by (1/2)ε̂ᵢ², the learned-path score error energy (Proposition 3.1).", "A matching lower bound on the chi-squared divergence, χ²(p̂^(i+1) ∥ qᵢ) ≥ (1/4)ηᵢε²⋆,ᵢ − C·ε⁴⋆,ᵢ, holds for small score errors (ε²⋆,ᵢ ≤ 1), where ηᵢ ∈ [0,1] is the observability coefficient (Proposition 3.3, Definition 3.2).", "Combining the upper and lower bounds yields a two-sided equivalence χ²(p̂^(i+1) ∥ qᵢ) ≍ ε²⋆,ᵢ in the perturbative regime (Theorem 3.4).", "When the score-error series ∑ᵢ ε²⋆,ᵢ diverges, the accumulated divergence across generations cannot vanish and has a non-zero floor limsup Dᵢ ≥ αη̄ε̄ / [16(1+(1-α)²)] (Proposition 4.1).", "When ∑ᵢ ε²⋆,ᵢ converges, accumulated divergence across generations satisfies D_{N+1} + C_bias ≍ Σᵢ(1-α)^{2(N-i)}ε²⋆,ᵢ + (1-α)^{2(N+1-i₀)}D_{i₀}, decaying geometrically at rate (1-α)² per generation, where α is the fraction of fresh data mixed in each round (Theorem 4.2).", "The α-dependent tradeoff between drift and stability is empirically confirmed on a 10D Gaussian mixture and on Fashion-MNIST/CIFAR-10, with low α producing collapse-driven drift and high α maintaining stability (Figure 1).", ] SOURCE_HASHES = { "paper_v1.pdf": "fe979c798cd48a5af02f6c647ecc29b2d6a937841adfa8ceedb492b1c2d81583", "source_v1.tar": "729a62da953ae2f9458f5e938ba53a7d6dbd8efbac6f079028fa6af6df0c0a06", "Fig1_Accumulation_alpha0.1.pdf": "45eebb04f9604968d8b8287f242306a41ffd0b76a16da9524b935800e1d1c5e7", "Fig1_Accumulation_alpha0.5.pdf": "0ae881662d2dd33a959a6c7c0cad78f74d0ea052ba9be66bba44dbc284a53798", "Fig1_Accumulation_alpha0.9.pdf": "b4522ecd3f09190dfbbfc3763809df8d650730913ca548e0ca78002e62cd9387", "Memory_Heatmap.pdf": "3cf3f1e29336c38af239a264149a755527e1137e8bdb64c57370c721ff8222d9", "fashionmnist_memory_alpha_0.1.png": "f790ec1387c2124743147fb70b562525210f4a86d3010d520ee33e8ecefea745", "fashionmnist_memory_alpha_0.5.png": "a95679ee2eda087e40e6b31416129bf1ebe20428849cf377431c0c32fa5dd097", "fashionmnist_memory_alpha_0.9.png": "1e3924dc05187a1a060ab085d2fce7b42f29efb4b10ec280cb4c75a2c5b423f6", "cifar10_observability.pdf": "1d44080768d8ea7a58122ac6938690d2275bc61e9fa0aaa18b5e0e496a094d4d", } def sha256(path: Path) -> str: value = hashlib.sha256() with path.open("rb") as handle: for block in iter(lambda: handle.read(1 << 20), b""): value.update(block) return value.hexdigest() def write_csv(path: Path, rows: list[dict]) -> None: with path.open("w", newline="", encoding="utf-8") as handle: writer = csv.DictWriter(handle, fieldnames=list(rows[0]), lineterminator="\n") writer.writeheader() writer.writerows(rows) def gaussian_kl(error_energy: float) -> float: """KL(N(mu,I)||N(0,I)) with ||mu||^2=error_energy.""" return 0.5 * error_energy def gaussian_chi2(error_energy: float) -> float: """chi^2(N(mu,I)||N(0,I)) with ||mu||^2=error_energy.""" return math.expm1(error_energy) def discounted_convolution(errors: np.ndarray, alpha: float, initial: float = 0.0) -> np.ndarray: beta = (1.0 - alpha) ** 2 values = np.empty(len(errors), dtype=float) current = float(initial) for index, error in enumerate(errors): current = beta * current + float(error) values[index] = current return values def direct_convolution(errors: np.ndarray, alpha: float, initial: float = 0.0) -> np.ndarray: beta = (1.0 - alpha) ** 2 rows = [] for generation in range(len(errors)): powers = beta ** np.arange(generation, -1, -1) rows.append(float(np.dot(powers, errors[: generation + 1]) + beta ** (generation + 1) * initial)) return np.asarray(rows) def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--output", default="outputs") parser.add_argument("--source-dir", default="sources") args = parser.parse_args() out = Path(args.output); src = Path(args.source_dir) out.mkdir(parents=True, exist_ok=True) source_rows = [] for filename, expected in SOURCE_HASHES.items(): actual = sha256(src / filename) source_rows.append({"file": filename, "expected_sha256": expected, "actual_sha256": actual, "match": expected == actual}) source_claim_rows = [ {"claim": 1, "source": "arXiv v1 Proposition 3.1, Eq. 16", "status": "supported", "scope": "A1 finite learned-path energy and A2 true Girsanov martingale; KL direction is learned endpoint to training target"}, {"claim": 2, "source": "arXiv v1 Definition 3.2 and Proposition 3.3, Eqs. 17/20", "status": "supported", "scope": "A1-A4, i>=i0, epsilon_star^2<=1; C depends on regularity/observability constants"}, {"claim": 3, "source": "arXiv v1 Theorem 3.4", "status": "supported", "scope": "A1-A4, positive observability and tail effects; equivalence constants are not one"}, {"claim": 4, "source": "arXiv v1 Proposition 4.1", "status": "literal_conflation_unsupported", "scope": "non-summability yields non-summable drift/cannot vanish; the displayed positive floor additionally requires a uniform per-generation error floor"}, {"claim": 5, "source": "arXiv v1 Theorem 4.2, Eq. 23", "status": "supported", "scope": "A1-A5, positive observability, small summable errors, and C_bias; asymp hides constants"}, {"claim": 6, "source": "arXiv v1 Figures 1,5-10 and Appendix G", "status": "supported", "scope": "10D GMM is quantitative; Fashion/CIFAR divergence uses learned-feature Gaussian proxies and is source evidence, not rerun here"}, ] # Claim 1: a Gaussian shift realizes equality in the path-KL identity. kl_rows = [] for dimension in [1, 2, 5, 10]: for amplitude in [0.02, 0.05, 0.1, 0.2, 0.4]: energy = dimension * amplitude * amplitude kl = gaussian_kl(energy) kl_rows.append({ "dimension": dimension, "coordinate_shift": amplitude, "learned_path_error_energy": f"{energy:.12e}", "endpoint_kl": f"{kl:.12e}", "half_energy_upper": f"{0.5*energy:.12e}", "upper_bound_holds": kl <= 0.5*energy + 1e-15, "ratio_to_half_energy": f"{kl/(0.5*energy):.12f}", }) # Claims 2-3: exact observability for deterministic Brownian integrands. # eta=(integral e)^2 / integral e^2 on [0,1]. Each base profile has unit energy. profiles = [ ("constant", 1.0), ("linear_increasing", 0.75), ("mean_zero_time_control", 0.0), ] observability_rows = [] sandwich_rows = [] for profile, eta in profiles: for scale in [0.02, 0.05, 0.1, 0.2, 0.35, 0.5]: path_energy = scale * scale endpoint_energy = eta * path_energy chi2 = gaussian_chi2(endpoint_energy) observability_rows.append({ "profile": profile, "scale": scale, "path_energy": f"{path_energy:.12e}", "observability_eta": f"{eta:.12f}", "endpoint_shift_energy": f"{endpoint_energy:.12e}", "endpoint_chi2": f"{chi2:.12e}", "eta_in_unit_interval": 0.0 <= eta <= 1.0, }) if eta > 0: leading = eta * path_energy sandwich_rows.append({ "profile": profile, "scale": scale, "path_energy": f"{path_energy:.12e}", "observability_eta": f"{eta:.12f}", "chi2": f"{chi2:.12e}", "chi2_over_eta_energy": f"{chi2/leading:.12f}", "one_quarter_leading_bound": f"{0.25*leading:.12e}", "leading_lower_holds_without_remainder": chi2 >= 0.25*leading, "chi2_over_path_energy": f"{chi2/path_energy:.12f}", }) # Claim 4: keep the two distinct premises of Proposition 4.1 distinct. generation_count = 6000 constant_error = np.full(generation_count, 0.02) harmonic_error = 0.02 / (np.arange(generation_count, dtype=float) + 1.0) persistent_rows = [] for alpha in [0.1, 0.5, 0.9]: beta = (1-alpha)**2 constant_d = discounted_convolution(constant_error, alpha) harmonic_d = discounted_convolution(harmonic_error, alpha) eta_floor = 0.4 source_floor = alpha * eta_floor * 0.02 / (16.0*(1.0+beta)) persistent_rows.append({ "alpha": alpha, "beta": f"{beta:.12f}", "constant_error_series_diverges": True, "constant_error_has_positive_floor": True, "constant_final_divergence": f"{constant_d[-1]:.12e}", "constant_exact_limit": f"{0.02/(1-beta):.12e}", "source_floor_with_eta_0_4": f"{source_floor:.12e}", "constant_exceeds_source_floor": constant_d[-1] >= source_floor, "harmonic_error_partial_sum": f"{harmonic_error.sum():.12e}", "harmonic_series_diverges_as_horizon_grows": True, "harmonic_error_has_positive_floor": False, "harmonic_final_divergence": f"{harmonic_d[-1]:.12e}", "harmonic_tail_tends_to_zero": harmonic_d[-1] < harmonic_d[generation_count//2], "literal_claim_floor_follows_from_divergence_alone": False, }) # Claim 5: summable errors and the exact geometric convolution identity. summable_errors = 0.08 / np.square(np.arange(1, 241, dtype=float)) accumulation_rows = [] memory_rows = [] for alpha in [0.0, 0.1, 0.5, 0.9]: beta = (1-alpha)**2 recurrence = discounted_convolution(summable_errors, alpha, initial=0.07) direct = direct_convolution(summable_errors, alpha, initial=0.07) for generation in [0, 1, 2, 5, 10, 20, 50, 100, 239]: accumulation_rows.append({ "alpha": alpha, "beta": f"{beta:.12f}", "generation": generation, "error_energy": f"{summable_errors[generation]:.12e}", "recurrence_value": f"{recurrence[generation]:.12e}", "direct_convolution": f"{direct[generation]:.12e}", "identity_residual": f"{abs(recurrence[generation]-direct[generation]):.3e}", }) if beta == 0: half_life = 0.0 elif beta == 1: half_life = math.inf else: half_life = math.log(0.5)/math.log(beta) memory_rows.append({ "alpha": alpha, "geometric_rate_beta": f"{beta:.12f}", "one_generation_retention": f"{beta:.12f}", "five_generation_retention": f"{beta**5:.12e}", "twenty_generation_retention": f"{beta**20:.12e}", "memory_half_life_generations": "inf" if math.isinf(half_life) else f"{half_life:.12f}", "final_discounted_divergence": f"{recurrence[-1]:.12e}", }) # Claim 6 source assets: record the exact qualitative statements they depict. experiment_rows = [ {"dataset": "10D five-component Gaussian mixture", "source_assets": "Fig1_Accumulation_alpha0.1/0.5/0.9.pdf; Memory_Heatmap.pdf", "fresh_data_levels": "0.1,0.5,0.9", "reported_outcome": "low alpha accumulates drift; high alpha has short geometric memory", "evidence_type": "pinned source experiment"}, {"dataset": "Fashion-MNIST", "source_assets": "fashionmnist_memory_alpha_0.1/0.5/0.9.png", "fresh_data_levels": "0.1,0.5,0.9", "reported_outcome": "memory heatmaps show longer persistence at low alpha", "evidence_type": "pinned source experiment"}, {"dataset": "CIFAR-10", "source_assets": "cifar10_observability.pdf", "fresh_data_levels": "0.1,0.5,0.9", "reported_outcome": "observability stabilizes around 0.25-0.35; feature-space proxy is used", "evidence_type": "pinned source experiment"}, ] gates = { "all_source_hashes_match": all(row["match"] for row in source_rows), "all_six_claims_mapped": len(source_claim_rows) == 6, "claim1_kl_upper_all_20_panels": all(row["upper_bound_holds"] for row in kl_rows), "claim1_gaussian_path_identity_exact": all(abs(float(row["ratio_to_half_energy"])-1.0) < 1e-12 for row in kl_rows), "claim2_eta_unit_interval": all(row["eta_in_unit_interval"] for row in observability_rows), "claim2_lower_leading_term_all_observable_panels": all(row["leading_lower_holds_without_remainder"] for row in sandwich_rows), "claim2_unobservable_control_has_energy": any(row["profile"] == "mean_zero_time_control" and float(row["path_energy"]) > 0 and float(row["endpoint_chi2"]) == 0 for row in observability_rows), "claim3_two_sided_ratio_finite_positive": all(0.70 <= float(row["chi2_over_path_energy"]) <= 1.30 for row in sandwich_rows), "claim3_small_error_ratio_near_observability": all(1.0 <= float(row["chi2_over_eta_energy"]) <= 1.04 for row in sandwich_rows if float(row["path_energy"]) <= 0.04), "claim4_constant_floor_panels_pass": all(row["constant_exceeds_source_floor"] for row in persistent_rows), "claim4_literal_conflation_detected": all(not row["literal_claim_floor_follows_from_divergence_alone"] for row in persistent_rows), "claim4_harmonic_nonfloor_control": all(row["harmonic_tail_tends_to_zero"] and not row["harmonic_error_has_positive_floor"] for row in persistent_rows), "claim5_summable_error_series": float(summable_errors.sum()) < 0.14, "claim5_convolution_identity": max(float(row["identity_residual"]) for row in accumulation_rows) < 1e-14, "claim5_geometric_rates_exact": all(abs(float(row["geometric_rate_beta"]) - (1-float(row["alpha"]))**2) < 1e-12 for row in memory_rows), "claim5_fresh_data_shortens_memory": float(memory_rows[1]["twenty_generation_retention"]) > float(memory_rows[2]["twenty_generation_retention"]) > float(memory_rows[3]["twenty_generation_retention"]), "claim5_no_fresh_data_destructive_control": memory_rows[0]["memory_half_life_generations"] == "inf" and float(memory_rows[0]["twenty_generation_retention"]) == 1.0, "claim6_all_three_source_datasets_pinned": {row["dataset"] for row in experiment_rows} == {"10D five-component Gaussian mixture", "Fashion-MNIST", "CIFAR-10"}, "claim6_three_alpha_levels_pinned": all(row["fresh_data_levels"] == "0.1,0.5,0.9" for row in experiment_rows), } write_csv(out / "source_pins.csv", source_rows) write_csv(out / "source_claim_audit.csv", source_claim_rows) write_csv(out / "gaussian_kl_upper.csv", kl_rows) write_csv(out / "observability_controls.csv", observability_rows) write_csv(out / "two_sided_sandwich.csv", sandwich_rows) write_csv(out / "persistent_error_audit.csv", persistent_rows) write_csv(out / "summable_accumulation.csv", accumulation_rows) write_csv(out / "fresh_data_memory.csv", memory_rows) write_csv(out / "source_experiments.csv", experiment_rows) figure, axes = plt.subplots(2, 2, figsize=(12, 8)) for profile in ["constant", "linear_increasing"]: rows = [row for row in sandwich_rows if row["profile"] == profile] axes[0, 0].loglog([float(row["path_energy"]) for row in rows], [float(row["chi2"]) for row in rows], "o-", label=profile) axes[0, 0].set(title="Observable Gaussian endpoint χ²", xlabel="path energy", ylabel="χ²"); axes[0, 0].legend() alphas = [0.1, 0.5, 0.9] for alpha in alphas: axes[0, 1].plot(np.arange(80), discounted_convolution(np.full(80, 0.02), alpha), label=f"alpha={alpha}") axes[0, 1].set(title="Persistent error floor", xlabel="generation", ylabel="discounted divergence"); axes[0, 1].legend() for alpha in [0.1, 0.5, 0.9]: axes[1, 0].semilogy(np.arange(80), discounted_convolution(summable_errors[:80], alpha, 0.07), label=f"alpha={alpha}") axes[1, 0].set(title="Summable errors: fresh data shortens memory", xlabel="generation", ylabel="discounted divergence"); axes[1, 0].legend() beta_values = [(1-alpha)**2 for alpha in alphas] ages = np.arange(21) for alpha, beta in zip(alphas, beta_values): axes[1, 1].semilogy(ages, np.maximum(beta**ages, 1e-20), "o-", label=f"alpha={alpha}") axes[1, 1].set(title="Exact memory kernel (1-alpha)^(2m)", xlabel="error age m", ylabel="retained weight"); axes[1, 1].legend() figure.tight_layout(); figure.savefig(out / "diffusion_collapse_audit.png", dpi=170); plt.close(figure) result = { "paper": {"openreview_id": "QYA0Q28ssf", "arxiv_id": "2602.16601v1", "title": "Quantifying Error Propagation and Model Collapse in Diffusion Models"}, "claims": CLAIMS, "scope": { "claim4": "Literal live claim conflates Proposition 4.1(i)'s non-summability premise with part (ii)'s additional uniform error-floor premise; the displayed positive floor is not supported by divergence of the series alone.", "gaussian_audit": "Exact Gaussian endpoint/path identities and deterministic memory recurrences test the formulas but do not replace the paper's stochastic-process proofs.", "experiments": "10D GMM, Fashion-MNIST, and CIFAR-10 conclusions are pinned source evidence; image divergence uses the source's learned-feature Gaussian proxy and is not an independent rerun.", }, "diagnostics": {"summable_error_total_240": float(summable_errors.sum()), "harmonic_partial_sum_6000": float(harmonic_error.sum()), "constant_error": 0.02}, "gates": gates, "all_gates_pass": all(gates.values()), } (out / "results.json").write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8") if not result["all_gates_pass"]: raise SystemExit(f"failed gates: {[name for name, passed in gates.items() if not passed]}") print(f"PASS {sum(gates.values())}/{len(gates)} gates") if __name__ == "__main__": main()