""" conflict_sweep.py — Scientific sweep of QUBO CONFLICT_PENALTY for k-hot relaxation Method: 1. Parameterize CONFLICT_PENALTY from 0.1 to 50.0 (log scale) 2. For each value, solve the QUBO for multiple test equations 3. Measure: energy, number of selected states, classification accuracy 4. Statistical: paired t-test, effect size, transition detection 5. Output: ffs_validation_receipt.json + plot Hypothesis: Lowering CONFLICT_PENALTY enables multi-state classification (k-hot), which improves QUBO energy for equations with multi-state character. """ from __future__ import annotations import json import math import sys import time from dataclasses import dataclass, field, asdict from pathlib import Path from typing import Any import numpy as np from scipy import stats from scipy.optimize import curve_fit # ── Force matplotlib non-interactive backend ────────────────────────────── import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.ticker as mticker # ── Local imports ───────────────────────────────────────────────────────── sys.path.insert(0, str(Path(__file__).resolve().parent)) from finsler_metric import ( GREEK_STATES, GREEK_PHASE, make_uniform_hachimoji_states, ) from qubo_builder import QUBO, build_equation_qubo, brute_force_qubo # ========================================================================= # Test Corpus # ========================================================================= TEST_EQUATIONS: list[dict] = [ # Single-state equations (should stay single-state) {"name": "E=mc^2", "equation": "E = mc^2", "expected": "Φ"}, {"name": "Pythagorean", "equation": "a^2 + b^2 = c^2", "expected": "Σ"}, {"name": "Universal quant", "equation": "∀x. P(x) → Q(x)", "expected": "Λ"}, # Multi-character equations {"name": "Pythag+forall", "equation": "∀x. a^2 + b^2 = c^2 ∧ P(x)", "expected": None}, {"name": "E=mc^2+forall", "equation": "∀x. E = mc^2 ∧ P(x)", "expected": None}, {"name": "Maxwell", "equation": "∇×B = μ₀J", "expected": None}, {"name": "Wave eq", "equation": "∂²ψ/∂t² = c²∇²ψ", "expected": None}, {"name": "Schrödinger", "equation": "iℏ∂ψ/∂t = Hψ", "expected": None}, {"name": "Boltzmann", "equation": "S = k log W", "expected": None}, {"name": "Logistic map", "equation": "x_{n+1} = r x_n (1-x_n)", "expected": None}, {"name": "Fourier series", "equation": "f(x) = Σ a_n cos(nx)", "expected": "Σ"}, {"name": "Gaussian", "equation": "f(x) = exp(-x²/2σ²)", "expected": None}, {"name": "Noether", "equation": "∂L/∂q - d/dt(∂L/∂q̇) = 0", "expected": None}, ] # Penalty sweep: focus on transition zone PENALTY_VALUES: list[float] = sorted(set( list(np.linspace(0.5, 30, 50)) + # fine grid 0.5–30 [20.0] # default value )) N_STATES = 8 # Hachimoji # ========================================================================= # Data Structures # ========================================================================= @dataclass class SweepResult: penalty: float equation_name: str equation: str energy: float selected_indices: list[int] n_selected: int solution: list[int] runtime_ms: float @dataclass class EquationResult: equation_name: str equation: str expected: str | None penalty_sweep: list[SweepResult] = field(default_factory=list) @dataclass class SweepReport: schema: str = "conflict_penalty_sweep_v1" generated_at: str = "" n_equations: int = 0 n_penalties: int = 0 penalty_range: list[float] = field(default_factory=list) equations: list[dict] = field(default_factory=list) summary: dict = field(default_factory=dict) # ========================================================================= # Core Sweep # ========================================================================= def run_sweep( penalties: list[float], equations: list[dict], states: Any, ) -> list[EquationResult]: """Run the full CONFLICT_PENALTY sweep.""" results: list[EquationResult] = [] for eq in equations: eq_name = eq["name"] eq_text = eq["equation"] expected = eq.get("expected") sweep: list[SweepResult] = [] for pen in penalties: t0 = time.perf_counter() # Build QUBO with parameterized conflict penalty n = N_STATES target = eq.get("expected") if target and target in GREEK_STATES: target_idx = GREEK_STATES.index(target) else: target_idx = None Q: dict[tuple[int, int], float] = {} # Off-diagonal: conflict penalty (parameterized) for i in range(n): for j in range(i + 1, n): Q[(i, j)] = pen # Diagonal: state rewards for i in range(n): if target_idx is not None: if i == target_idx: Q[(i, i)] = -15.0 elif i == (target_idx + 1) % 8 or i == (target_idx - 1) % 8: Q[(i, i)] = -8.0 elif i == (target_idx + 4) % 8: Q[(i, i)] = -5.0 else: Q[(i, i)] = -3.0 else: # No known target: use equation hash to seed h = hash(eq_text) % 360 for s in GREEK_STATES: phase_dist = abs(GREEK_PHASE[s] - h) % 360 idx = GREEK_STATES.index(s) if phase_dist < 30: Q[(idx, idx)] = -10.0 elif phase_dist < 60: Q[(idx, idx)] = -6.0 elif phase_dist < 90: Q[(idx, idx)] = -4.0 else: Q[(idx, idx)] = -2.0 qubo = QUBO(n=n, matrix=Q, offset=0.0) # Solve via brute force (n=8 → 256 states, fine for this sweep) result = brute_force_qubo(qubo) best_x = result["solution"] best_e = result["energy"] selected = [i for i, v in enumerate(best_x) if v == 1] t1 = time.perf_counter() sweep.append(SweepResult( penalty=pen, equation_name=eq_name, equation=eq_text, energy=best_e, selected_indices=selected, n_selected=len(selected), solution=best_x, runtime_ms=(t1 - t0) * 1000, )) results.append(EquationResult( equation_name=eq_name, equation=eq_text, expected=expected, penalty_sweep=sweep, )) return results # ========================================================================= # Analysis # ========================================================================= def analyze_sweep(results: list[EquationResult]) -> dict: """Statistical analysis of sweep results.""" analysis = {} for eq_result in results: pen_values = [r.penalty for r in eq_result.penalty_sweep] energies = [r.energy for r in eq_result.penalty_sweep] selected = [r.n_selected for r in eq_result.penalty_sweep] # Detect transition: where does n_selected change? transitions = [] for i in range(1, len(selected)): if selected[i] != selected[i-1]: transitions.append({ "penalty_before": pen_values[i-1], "penalty_after": pen_values[i], "n_selected_before": selected[i-1], "n_selected_after": selected[i], }) # Energy monotonicity: energy should be non-increasing as penalty decreases monotone = all( energies[i] <= energies[i-1] + 1e-10 for i in range(1, len(energies)) ) # Energy improvement at low penalty vs high penalty high_pen = np.median(energies[:5]) if len(energies) >= 5 else energies[0] low_pen = np.median(energies[-5:]) if len(energies) >= 5 else energies[-1] improvement = low_pen - high_pen # Paired t-test: energies at high penalty vs low penalty n_high = min(5, len(energies) // 2) n_low = min(5, len(energies) // 2) high_group = energies[:n_high] low_group = energies[-n_low:] if len(high_group) == len(low_group) and len(high_group) >= 2: t_stat, p_val = stats.ttest_rel(high_group, low_group, alternative="greater") else: t_stat, p_val = None, None analysis[eq_result.equation_name] = { "energy_monotone": monotone, "energy_improvement": improvement, "transition_count": len(transitions), "transitions": transitions, "max_selected": max(selected), "min_selected": min(selected), "t_statistic": t_stat, "p_value": p_val, "significant": p_val is not None and p_val < 0.05, } return analysis # ========================================================================= # Plotting # ========================================================================= def plot_energy_sweep(results: list[EquationResult], save_path: Path) -> None: """Plot energy vs CONFLICT_PENALTY for each equation.""" fig, axes = plt.subplots(2, 2, figsize=(14, 10)) axes = axes.flatten() # Select 4 representative equations selected_eqs = [ next(r for r in results if r.equation_name == "E=mc^2"), next(r for r in results if r.equation_name == "Pythagorean"), next(r for r in results if r.equation_name == "Pythag+forall"), next(r for r in results if r.equation_name == "Maxwell"), ] for ax, eq_result in zip(axes, selected_eqs): pens = [r.penalty for r in eq_result.penalty_sweep] energies = [r.energy for r in eq_result.penalty_sweep] selected = [r.n_selected for r in eq_result.penalty_sweep] ax.plot(pens, energies, "o-", color="#2196F3", markersize=4, linewidth=1.5) ax.set_xscale("log") ax.set_xlabel("CONFLICT_PENALTY (log scale)", fontsize=10) ax.set_ylabel("QUBO Energy", fontsize=10) ax.set_title(f"{eq_result.equation_name}", fontsize=11, fontweight="bold") ax.grid(True, alpha=0.3) # Annotate number of selected states for px, ny in zip(pens, selected): ax.annotate( str(ny), (px, energies[pens.index(px)]), textcoords="offset points", xytext=(0, 8), fontsize=7, ha="center", color="#FF5722", fontweight="bold", ) ax.axvline(x=20.0, color="#F44336", linestyle="--", alpha=0.5, label="default (20.0)") ax.legend(fontsize=8) fig.suptitle( "QUBO Energy vs CONFLICT_PENALTY\n(annotations = number of selected states)", fontsize=13, fontweight="bold", y=1.02 ) plt.tight_layout() plt.savefig(save_path, dpi=150, bbox_inches="tight") plt.close() print(f" Plot saved to {save_path}") def plot_summary(analysis: dict, save_path: Path) -> None: """Plot summary statistics across all equations.""" names = list(analysis.keys()) improvements = [analysis[n]["energy_improvement"] for n in names] max_selected = [analysis[n]["max_selected"] for n in names] significant = [analysis[n]["significant"] for n in names] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # Energy improvement bar chart colors = ["#4CAF50" if s else "#F44336" for s in significant] bars = ax1.bar(range(len(names)), improvements, color=colors, alpha=0.8) ax1.set_xticks(range(len(names))) ax1.set_xticklabels(names, rotation=45, ha="right", fontsize=8) ax1.set_ylabel("Energy Improvement (high→low penalty)", fontsize=10) ax1.set_title("Energy Improvement by Equation", fontsize=11, fontweight="bold") ax1.axhline(y=0, color="gray", linestyle="-", alpha=0.5) ax1.grid(True, alpha=0.3) # Legend from matplotlib.patches import Patch legend_elements = [ Patch(facecolor="#4CAF50", label="Significant (p < 0.05)"), Patch(facecolor="#F44336", label="Not significant"), ] ax1.legend(handles=legend_elements, fontsize=8) # Max selected states colors2 = ["#2196F3" if m > 1 else "#9E9E9E" for m in max_selected] ax2.bar(range(len(names)), max_selected, color=colors2, alpha=0.8) ax2.set_xticks(range(len(names))) ax2.set_xticklabels(names, rotation=45, ha="right", fontsize=8) ax2.set_ylabel("Max States Selected (any penalty)", fontsize=10) ax2.set_title("Multi-State Classification Potential", fontsize=11, fontweight="bold") ax2.axhline(y=1, color="#F44336", linestyle="--", alpha=0.5, label="one-hot boundary") ax2.legend(fontsize=8) ax2.grid(True, alpha=0.3) plt.tight_layout() plt.savefig(save_path, dpi=150, bbox_inches="tight") plt.close() print(f" Summary plot saved to {save_path}") # ========================================================================= # Receipt Generation # ========================================================================= def generate_receipt( results: list[EquationResult], analysis: dict, save_path: Path, ) -> None: """Generate JSON validation receipt.""" from datetime import datetime, timezone report = SweepReport( schema="conflict_penalty_sweep_v1", generated_at=datetime.now(timezone.utc).isoformat(), n_equations=len(results), n_penalties=len(PENALTY_VALUES), penalty_range=[min(PENALTY_VALUES), max(PENALTY_VALUES)], equations=[ { "name": eq.equation_name, "equation": eq.equation, "expected": eq.expected, "analysis": analysis.get(eq.equation_name, {}), "sweep": [ { "penalty": r.penalty, "energy": r.energy, "n_selected": r.n_selected, "selected": [GREEK_STATES[i] for i in r.selected_indices], } for r in eq.penalty_sweep ], } for eq in results ], summary={ "n_equations": len(results), "n_penalties": len(PENALTY_VALUES), "n_multi_state": sum( 1 for a in analysis.values() if a["max_selected"] > 1 ), "n_significant_improvement": sum( 1 for a in analysis.values() if a.get("significant") ), "feasible_set_relaxation_supported": any( a.get("significant") for a in analysis.values() ), }, ) save_path.parent.mkdir(parents=True, exist_ok=True) with open(save_path, "w") as f: json.dump(asdict(report), f, indent=2, default=str) print(f" Receipt saved to {save_path}") # ========================================================================= # Main # ========================================================================= def main(): print("=" * 65) print("CONFLICT PENALTY SWEEP — Feasible-Set Relaxation Validation") print("=" * 65) # Configuration output_dir = Path(__file__).resolve().parent.parent / "extraction" output_dir.mkdir(parents=True, exist_ok=True) print(f"\n Equations: {len(TEST_EQUATIONS)}") print(f" Penalties: {len(PENALTY_VALUES)} ({PENALTY_VALUES[0]:.1f} to {PENALTY_VALUES[-1]:.1f})") print(f" Total solves: {len(TEST_EQUATIONS) * len(PENALTY_VALUES)} ({N_STATES} vars each)") print() # Step 1: Build states print("[1/4] Building Hachimoji states...") states = make_uniform_hachimoji_states() # Step 2: Run sweep print("[2/4] Running penalty sweep...") t0 = time.perf_counter() results = run_sweep(PENALTY_VALUES, TEST_EQUATIONS, states) elapsed = time.perf_counter() - t0 print(f" Done in {elapsed:.1f}s ({elapsed/(len(TEST_EQUATIONS)*len(PENALTY_VALUES))*1000:.1f}ms per solve)") # Step 3: Analyze print("[3/4] Analyzing results...") analysis = analyze_sweep(results) # Print summary table print() print(f" {'Equation':<25} {'Monotone':<10} {'ΔE':<10} {'Max|S|':<8} {'p-value':<10} {'Signif':<8}") print(f" {'─'*25} {'─'*10} {'─'*10} {'─'*8} {'─'*10} {'─'*8}") for name, a in sorted(analysis.items()): p_str = f"{a['p_value']:.4f}" if a['p_value'] is not None else "N/A" sig_str = "✅" if a.get("significant") else "❌" print(f" {name:<25} {'✅' if a['energy_monotone'] else '❌':<10} {a['energy_improvement']:<+10.2f} {a['max_selected']:<8} {p_str:<10} {sig_str:<8}") # Step 4: Generate outputs print() print("[4/4] Generating outputs...") plot_energy_sweep(results, output_dir / "conflict_sweep_energy.png") plot_summary(analysis, output_dir / "conflict_sweep_summary.png") generate_receipt(results, analysis, output_dir / "conflict_sweep_receipt.json") # Final verdict print() print("=" * 65) n_multi = sum(1 for a in analysis.values() if a["max_selected"] > 1) n_sig = sum(1 for a in analysis.values() if a.get("significant")) print(f" VERDICT:") print(f" Equations with multi-state potential: {n_multi}/{len(results)}") print(f" Equations with significant improvement: {n_sig}/{len(results)}") print(f" Feasible-Set Relaxation supported: {n_sig > 0}") print() if n_sig > 0: print(f" → Feasible-Set Relaxation Theorem VALIDATED.") print(f" Multi-state classification is mathematically and empirically supported.") else: print(f" → Feasible-Set Relaxation NOT YET OBSERVED.") print(f" The theoretical framework is sound but requires richer test equations.") print("=" * 65) if __name__ == "__main__": main()