"""Direct checks of the released Theorem 4.6 geometric algorithm.""" from __future__ import annotations import math import time from collections.abc import Iterator from pathlib import Path from typing import Any import numpy as np import PLD_accounting.geometric_convolution as geom_module from PLD_accounting import ( AllocationSchemeConfig, BoundType, ConvolutionMethod, PrivacyParams, gaussian_allocation_epsilon_configurable, ) from PLD_accounting.discrete_dist import DenseDiscreteDist, Domain from PLD_accounting.types import SpacingType from evidence_utils import ( ARTIFACTS, FIXED_COMMAND, manifest, runtime_metadata, write_csv, write_json, write_text, ) PROBABILITIES = np.array([0.07, 0.19, 0.31, 0.43], dtype=np.float64) TARGET_VALUES = np.array([0.5, 1.0, 2.0, 4.0], dtype=np.float64) def binary_call_count(t: int) -> int: return math.floor(math.log2(t)) + t.bit_count() - 1 def count_vectors(total: int, dimensions: int) -> Iterator[tuple[int, ...]]: if dimensions == 1: yield (total,) return for first in range(total + 1): for rest in count_vectors(total - first, dimensions - 1): yield (first, *rest) def make_grid_distribution( total_alpha: float, operations: int ) -> tuple[DenseDiscreteDist, list[tuple[float, float]], float]: """Place four fixed atoms on a fine geometric grid spanning [0.5, 4].""" log_step = total_alpha / operations ratio = math.exp(log_step) bins = math.ceil(math.log(4.0 / 0.5) / log_step) + 1 masses = np.zeros(bins, dtype=np.float64) indices = np.rint(np.log(TARGET_VALUES / 0.5) / log_step).astype(int) for index, probability in zip(indices, PROBABILITIES, strict=True): masses[index] += probability dist = DenseDiscreteDist( x_0=0.5, step=ratio, prob_arr=masses, p_min=0.0, p_max=0.0, spacing_type=SpacingType.GEOMETRIC, domain=Domain.POSITIVES, ) nonzero = [ (float(dist.x_array[index]), float(masses[index])) for index in np.flatnonzero(masses) ] return dist, nonzero, log_step def exact_sum_atoms( base_atoms: list[tuple[float, float]], t: int ) -> list[tuple[float, float]]: """Independent multinomial enumeration of the t-fold exact sum.""" atoms = [] factorial_t = math.factorial(t) for counts in count_vectors(t, len(base_atoms)): coefficient = factorial_t value = 0.0 probability = 1.0 for count, (atom, mass) in zip(counts, base_atoms, strict=True): coefficient //= math.factorial(count) value += count * atom probability *= mass**count atoms.append((math.log(value), coefficient * probability)) return atoms def dense_log_atoms(dist: DenseDiscreteDist) -> list[tuple[float, float]]: atoms = [ (math.log(float(value)), float(mass)) for value, mass in zip(dist.x_array, dist.prob_arr, strict=True) if mass > 0 ] if dist.p_min: atoms.append((-math.inf, float(dist.p_min))) if dist.p_max: atoms.append((math.inf, float(dist.p_max))) return atoms def ccdf(atoms: list[tuple[float, float]], threshold: float) -> float: return math.fsum(mass for value, mass in atoms if value > threshold) def stochastic_contract( exact: list[tuple[float, float]], upper: list[tuple[float, float]], alpha: float, beta: float, ) -> dict[str, Any]: finite_values = [ value for value, _ in exact + upper if math.isfinite(value) ] thresholds = set(finite_values) thresholds.update(np.nextafter(value, -math.inf) for value in finite_values) thresholds.update(value + alpha for value in finite_values) validity_violation = 0.0 tightness_violation = 0.0 for threshold in thresholds: exact_tail = ccdf(exact, threshold) upper_tail = ccdf(upper, threshold) validity_violation = max(validity_violation, exact_tail - upper_tail) tightness_violation = max( tightness_violation, upper_tail - ccdf(exact, threshold - alpha) - beta, ) tolerance = 2e-11 return { "validity_max_violation": validity_violation, "tightness_max_violation": tightness_violation, "valid": validity_violation <= tolerance, "alpha_beta_tight": tightness_violation <= tolerance, "exact_mass": math.fsum(mass for _, mass in exact), "upper_mass": math.fsum(mass for _, mass in upper), "thresholds_checked": len(thresholds), } def run_fast_case( t: int, alpha: float, beta: float, *, check_exact: bool ) -> dict[str, Any]: operations = binary_call_count(t) dist, base_atoms, log_step = make_grid_distribution(alpha, operations) calls: list[dict[str, int]] = [] original = geom_module.geometric_convolve def tracked_convolve(**kwargs): left = kwargs["dist_1"] right = kwargs["dist_2"] output = original(**kwargs) calls.append( { "left_bins": len(left.prob_arr), "right_bins": len(right.prob_arr), "output_bins": len(output.prob_arr), "pair_products": len(left.prob_arr) * len(right.prob_arr), } ) return output geom_module.geometric_convolve = tracked_convolve try: started = time.perf_counter() upper = geom_module.geometric_self_convolve( dist=dist, T=t, tail_truncation=beta, bound_type=BoundType.DOMINATES, ) runtime = time.perf_counter() - started finally: geom_module.geometric_convolve = original if check_exact: exact = exact_sum_atoms(base_atoms, t) contract = stochastic_contract( exact=exact, upper=dense_log_atoms(upper), alpha=alpha, beta=beta, ) # Negative control: lower rounding is invalid if mislabeled as an upper bound. lower = geom_module.geometric_self_convolve( dist=dist, T=t, tail_truncation=beta, bound_type=BoundType.IS_DOMINATED, ) mutation_contract = stochastic_contract( exact=exact, upper=dense_log_atoms(lower), alpha=alpha, beta=beta, ) negative_control = { "mutation": "use IS_DOMINATED rounding but label it DOMINATES", "rejected": not mutation_contract["valid"], "validity_max_violation": mutation_contract[ "validity_max_violation" ], } else: contract = { "validity_max_violation": None, "tightness_max_violation": None, "valid": None, "alpha_beta_tight": None, "exact_mass": None, "upper_mass": float( np.sum(upper.prob_arr) + upper.p_min + upper.p_max ), "thresholds_checked": 0, } negative_control = { "mutation": "not run: operation-count-only scaling case", "rejected": None, "validity_max_violation": None, } work = sum(call["pair_products"] for call in calls) return { "t": t, "alpha": alpha, "beta": beta, "exact_checked": check_exact, "stage_log_grid_step": log_step, "expected_convolution_calls": operations, "observed_convolution_calls": len(calls), "input_bins": len(dist.prob_arr), "output_bins": len(upper.prob_arr), "primitive_pair_products": work, "runtime_seconds": runtime, **contract, "negative_control": negative_control, "call_trace": calls, } def complexity_sweeps() -> tuple[list[dict[str, Any]], dict[str, Any]]: # Trigger JIT compilation outside recorded timing. run_fast_case(t=3, alpha=0.3, beta=0.0, check_exact=True) accuracy_rows = [ run_fast_case(t=t, alpha=alpha, beta=beta, check_exact=True) for t, alpha, beta in ( (3, 0.30, 0.0), (5, 0.20, 1e-8), (8, 0.15, 1e-8), (12, 0.10, 1e-8), ) ] t_rows = [ run_fast_case(t=t, alpha=0.2, beta=1e-8, check_exact=False) for t in (8, 16, 32, 64, 128, 256) ] alpha_rows = [ run_fast_case(t=64, alpha=alpha, beta=1e-8, check_exact=False) for alpha in (0.4, 0.2, 0.1, 0.05) ] beta_rows = [ run_fast_case(t=21, alpha=0.15, beta=beta, check_exact=False) for beta in (0.0, 1e-8, 1e-5) ] loglog_t = np.log([math.log(row["t"]) for row in t_rows]) log_work_t = np.log([row["primitive_pair_products"] for row in t_rows]) t_exponent = float(np.polyfit(loglog_t, log_work_t, 1)[0]) log_inv_alpha = np.log([1 / row["alpha"] for row in alpha_rows]) log_work_alpha = np.log( [row["primitive_pair_products"] for row in alpha_rows] ) alpha_exponent = float(np.polyfit(log_inv_alpha, log_work_alpha, 1)[0]) normalized = [ row["primitive_pair_products"] * row["alpha"] ** 2 / math.log(row["t"]) ** 3 for row in t_rows ] scaling = { "t_work_exponent_against_log_t": t_exponent, "alpha_work_exponent_against_inverse_alpha": alpha_exponent, "normalized_work_min": min(normalized), "normalized_work_max": max(normalized), "normalized_work_ratio": max(normalized) / min(normalized), "t_exponent_expected": 3.0, "alpha_exponent_expected": 2.0, "t_exponent_acceptance": [1.5, 4.5], "alpha_exponent_acceptance": [1.5, 2.5], "normalized_ratio_acceptance_max": 8.0, } return accuracy_rows + t_rows + alpha_rows + beta_rows, scaling def high_level_accuracy() -> list[dict[str, Any]]: """Check released Gaussian upper/lower outputs at explicit alpha and beta.""" rows = [] # Warm up the full author path separately. warm_params = PrivacyParams(sigma=2.0, num_steps=4, delta=1e-5) warm_config = AllocationSchemeConfig( loss_discretization=0.3, tail_truncation=1e-7, convolution_method=ConvolutionMethod.GEOM, ) gaussian_allocation_epsilon_configurable( warm_params, warm_config, bound_type=BoundType.DOMINATES ) for t in (16, 64, 256): for alpha in (0.2, 0.1): beta = 1e-8 config = AllocationSchemeConfig( loss_discretization=alpha, tail_truncation=beta, convolution_method=ConvolutionMethod.GEOM, ) params = PrivacyParams( sigma=2.0, num_steps=t, num_selected=1, delta=1e-5 ) started = time.perf_counter() upper = gaussian_allocation_epsilon_configurable( params, config, bound_type=BoundType.DOMINATES ) lower = gaussian_allocation_epsilon_configurable( params, config, bound_type=BoundType.IS_DOMINATED ) runtime = time.perf_counter() - started gap = upper - lower rows.append( { "t": t, "sigma": 2.0, "delta": 1e-5, "alpha": alpha, "beta": beta, "epsilon_upper": upper, "epsilon_lower": lower, "epsilon_gap": gap, "gap_bound_2alpha": 2 * alpha, "ordered": upper >= lower, "gap_within_2alpha": gap <= 2 * alpha + 1e-8, "runtime_seconds": runtime, } ) return rows def write_bundle( fast_rows: list[dict[str, Any]], scaling: dict[str, Any], high_level: list[dict[str, Any]], passed: bool, total_runtime: float, ) -> None: out = ARTIFACTS / "claim_2" write_json( out / "claim_contract.json", { "claim_id": 2, "source_statement": "Theorem 4.6 validity, (alpha,beta)-tightness, and O((IQR/alpha)^2 log^3(t)) runtime; Gaussian specialization adds log(t/beta)/sigma^2.", "machine_checks": [ "Exact independent multinomial sum versus released geometric self-convolution.", "CCDF validity and (alpha,beta) inequalities at every atom boundary.", "Exact exponentiation-by-squaring call count floor(log2 t)+popcount(t)-1.", "Primitive pair-product work scaling in log(t)^3 and alpha^-2.", "Released Gaussian upper/lower epsilon gap no greater than 2 alpha.", ], "verdict_rule": "VERIFIED iff all accuracy, operation-count, scaling, released-path checks pass and lower-rounding mutations are rejected.", }, ) write_text( out / "source_audit.md", """# Claim 2 source audit Theorem 4.6 is `body.tex` label `thm:num_acc_RA`, lines 266–280. Its general runtime is `O((IQR_{beta/t}/alpha)^2 log^3(t))`; for the unit-sensitivity Gaussian mechanism it specializes to `O(log_2^3(t) ln(t/beta)/(sigma^2 alpha^2))`. The algorithm outline at lines 253–262 specifies direct convolution, a geometrically spaced grid, domination-preserving directional rounding, and at most `2 ceil(log_2(t))` convolution steps. Appendix C states the exact binary count `floor(log_2(t)) + popcount(t) - 1`. The judge paraphrase omits the general IQR factor and the Gaussian `sigma^-2` factor. The contract tests the exact source statement. """, ) write_text( out / "method.md", """# Method The released implementation pinned in `uv.lock` is exercised directly. Four fixed atoms are placed on increasingly fine geometric grids. Its `geometric_self_convolve` output is compared to a separately implemented multinomial enumeration of the exact t-fold sum. CCDF inequalities are checked at all exact and rounded atom boundaries for both validity and `(alpha,beta)` tightness. The convolution function is wrapped only to count calls and input-bin pair products; numeric work is still performed by the unmodified released function. Sweeps over `t` and `alpha` fit the exponents of primitive work. A second suite calls the released Gaussian public API and checks its dominating and dominated epsilon bounds at explicit alpha/beta settings. The negative control substitutes downward rounding where an upper bound is required and must violate validity. """, ) serializable_rows = [ {key: value for key, value in row.items() if key != "call_trace"} for row in fast_rows ] write_json( out / "raw_results.json", { "fast_convolution_checks": fast_rows, "scaling": scaling, "released_gaussian_checks": high_level, }, ) write_csv( out / "complexity_raw.csv", [ "t", "alpha", "beta", "exact_checked", "stage_log_grid_step", "expected_convolution_calls", "observed_convolution_calls", "input_bins", "output_bins", "primitive_pair_products", "runtime_seconds", "validity_max_violation", "tightness_max_violation", "valid", "alpha_beta_tight", "exact_mass", "upper_mass", "thresholds_checked", ], [ { key: row[key] for key in ( "t", "alpha", "beta", "exact_checked", "stage_log_grid_step", "expected_convolution_calls", "observed_convolution_calls", "input_bins", "output_bins", "primitive_pair_products", "runtime_seconds", "validity_max_violation", "tightness_max_violation", "valid", "alpha_beta_tight", "exact_mass", "upper_mass", "thresholds_checked", ) } for row in serializable_rows ], ) write_csv( out / "released_gaussian_accuracy.csv", list(high_level[0].keys()), high_level, ) write_json( out / "independent_checker.json", { "implementation": "multinomial count-vector enumeration and direct CCDF inequalities", "fast_checks": serializable_rows, "scaling": scaling, }, ) write_json( out / "negative_control.json", [row["negative_control"] for row in fast_rows], ) metadata = runtime_metadata() metadata["runtime_seconds"] = total_runtime write_json(out / "exact_command_environment.json", metadata) write_text( out / "limitations.md", """# Limitations and deviations Asymptotic Big-O cannot be proven by finite timing. The gate therefore uses the released operation structure and primitive pair-product counts as its primary complexity evidence; wall time is recorded but not used as a brittle pass condition. Exact accuracy checks use finite four-atom inputs. The Gaussian API checks cover t up to 256 rather than the paper's largest application and use coarse alpha values so this branch remains a targeted CPU contract test. """, ) write_text( out / "EVAL.md", f"""# Claim 2 evaluation Verdict: **{'VERIFIED' if passed else 'FALSIFIED'}** - Direct fast-versus-exact cases: {len(fast_rows)} - Released Gaussian alpha/beta cases: {len(high_level)} - `log(t)` work exponent: {scaling['t_work_exponent_against_log_t']:.4f} - inverse-alpha work exponent: {scaling['alpha_work_exponent_against_inverse_alpha']:.4f} - normalized work spread: {scaling['normalized_work_ratio']:.4f}x - Runtime: {total_runtime:.6f} CPU wall-clock seconds """, ) def main() -> int: started = time.perf_counter() fast_rows, scaling = complexity_sweeps() high_level = high_level_accuracy() fast_ok = all( row["observed_convolution_calls"] == row["expected_convolution_calls"] and ( not row["exact_checked"] or ( row["valid"] and row["alpha_beta_tight"] and row["negative_control"]["rejected"] ) ) for row in fast_rows ) scaling_ok = ( 1.5 <= scaling["t_work_exponent_against_log_t"] <= 4.5 and 1.5 <= scaling["alpha_work_exponent_against_inverse_alpha"] <= 2.5 and scaling["normalized_work_ratio"] <= 8.0 ) high_level_ok = all( row["ordered"] and row["gap_within_2alpha"] for row in high_level ) passed = fast_ok and scaling_ok and high_level_ok runtime = time.perf_counter() - started write_bundle(fast_rows, scaling, high_level, passed, runtime) summary = { "claim_2": "VERIFIED" if passed else "FALSIFIED", "fast_accuracy_cases": len(fast_rows), "released_gaussian_cases": len(high_level), "fast_ok": fast_ok, "scaling_ok": scaling_ok, "released_gaussian_ok": high_level_ok, "runtime_seconds": runtime, "fixed_command": FIXED_COMMAND, } write_json(ARTIFACTS / "round_1b_summary.json", summary) write_json( ARTIFACTS / "round_1b_manifest.json", manifest(ARTIFACTS / "claim_2"), ) print("=" * 78) print("ROUND 1B — RELEASED GEOMETRIC ALGORITHM CONTRACT") print("=" * 78) print(f"Claim 2: {summary['claim_2']}") print( f"Fast cases={len(fast_rows)}; Gaussian API cases={len(high_level)}; " "negative controls=" f"{sum(r['negative_control']['rejected'] is True for r in fast_rows)}/" f"{sum(r['exact_checked'] for r in fast_rows)}" ) print( "Primitive-work exponents: " f"log(t)^{scaling['t_work_exponent_against_log_t']:.3f}, " f"alpha^-{scaling['alpha_work_exponent_against_inverse_alpha']:.3f}; " f"normalized spread={scaling['normalized_work_ratio']:.3f}x" ) print( f"Subchecks: accuracy={fast_ok}, scaling={scaling_ok}, " f"released Gaussian={high_level_ok}" ) print(f"SUMMARY_JSON={summary}") return 0 if passed else 1