"""F5-conf-03: regenerate out/results.json and out/counterexample.csv from data/ only. Run from workspace root: ~/wp29/venv/bin/python out/analysis.py """ import json, os import numpy as np import pandas as pd from scipy import stats os.makedirs("out", exist_ok=True) props = pd.read_csv("data/propositions.csv") design = pd.read_csv("data/design.csv") d = dict(zip(design["parameter"], design["value"])) seed = int(d["seed"]); n = int(d["n"]) data_issues = [] if set(props["id"]) != {"A", "B"}: data_issues.append({"issue": "unexpected proposition ids", "handling": "reported"}) # Verdicts rest on the mathematical argument in out/argument.md, not on simulation. statement_A_true = False # counterexample: Y = S*X, S Rademacher independent of X statement_B_true = True # independent normals: characteristic functions multiply rng = np.random.default_rng(seed) x = rng.standard_normal(n) s = rng.choice(np.array([-1.0, 1.0]), size=n) # independent Rademacher sign y = s * x z = x + y pd.DataFrame({"x": x, "y": y}).to_csv("out/counterexample.csv", index=False) def ks_vs_fitted(v): return float(stats.kstest(v, "norm", args=(v.mean(), v.std(ddof=1))).statistic) cv = { "construction": "X ~ N(0,1); S independent of X with P(S=1)=P(S=-1)=1/2; Y = S*X. " "Then Y ~ N(0,1) by symmetry of N(0,1), but X+Y = (1+S)X equals 0 with prob. 1/2 " "and 2X (N(0,4)) with prob. 1/2: a non-degenerate law with an atom at 0, hence not normal.", "n": int(n), "seed": seed, "mean_x": float(x.mean()), "sd_x": float(x.std(ddof=1)), "mean_y": float(y.mean()), "sd_y": float(y.std(ddof=1)), "mean_sum": float(z.mean()), "sd_sum": float(z.std(ddof=1)), "ks_sum_vs_normal": ks_vs_fitted(z), } # Supplementary diagnostics (illustration only) diag = { "frac_sum_exactly_zero": float(np.mean(z == 0.0)), "ks_x_vs_std_normal": float(stats.kstest(x, "norm").statistic), "ks_y_vs_std_normal": float(stats.kstest(y, "norm").statistic), "ks_x_vs_fitted_normal": ks_vs_fitted(x), "ks_y_vs_fitted_normal": ks_vs_fitted(y), "corr_xy": float(np.corrcoef(x, y)[0, 1]), } # Population sup distance between law of X+Y (0.5*delta_0 + 0.5*N(0,4)) and N(0,2) # (normal with the same mean 0 and variance 2), on a fine grid incl. both sides of the jump at 0. grid = np.linspace(-10, 10, 200001) F_mix = 0.5 * (grid >= 0) + 0.5 * stats.norm.cdf(grid, scale=2.0) F_mix_left = 0.5 * (grid > 0) + 0.5 * stats.norm.cdf(grid, scale=2.0) G = stats.norm.cdf(grid, scale=np.sqrt(2.0)) diag["population_ks_sum_vs_N02"] = float(max(np.abs(F_mix - G).max(), np.abs(F_mix_left - G).max())) diag["ks_95pct_critical_value_approx"] = float(1.358 / np.sqrt(n)) # Baseline for B (independent case), same seed stream continued, same KS rule xb = rng.standard_normal(n); yb = rng.standard_normal(n) baseline = {"baseline_b_ks_sum_vs_normal": ks_vs_fitted(xb + yb), "baseline_b_sd_sum": float((xb + yb).std(ddof=1))} results = { "task_id": "F5-conf-03", "statement_true": statement_A_true, "counterexample_provided": True, "counterexample_values": cv, "statement_b_true": statement_B_true, "feasibility": "complete", "feasibility_reason": None, "data_issues": data_issues, "n_input_rows": {"propositions.csv": int(len(props)), "design.csv": int(len(design))}, "n_analysed": int(n), "random_seed": seed, "counterexample_diagnostics": diag, **baseline, } with open("out/results.json", "w") as f: json.dump(results, f, indent=2) print(json.dumps({k: results[k] for k in ("statement_true", "statement_b_true")}), "KS(sum)=%.4f" % cv["ks_sum_vs_normal"], "baseline KS(B)=%.4f" % baseline["baseline_b_ks_sum_vs_normal"])