#!/usr/bin/env python3 """prime_sidon_explore.py — Integer-only Sidon sum-degeneracy exploration. Tests whether prime-based label sets satisfy the Sidon property (all pairwise sums distinct) across scales from small primes (2,3,5,7,11) up to quintillion (10^18). All arithmetic is pure integer. No float, no numpy eigenvalue decompositions, no SLOS, no tensor networks. The metric is the actual Sidon property: A set A is Sidon iff for all i,j,k,l, a_i + a_j = a_k + a_l ⇒ {i,j} = {k,l} For each label set we compute: - Total pairs: n^2 (including order, so (i,j) counts separately from (j,i)) - Distinct sums: number of unique values in the sum matrix - Sidon score: distinct_sums / n^2 (1.0 = perfect Sidon) - Max degeneracy: highest multiplicity of any single sum - Collision entropy: Shannon entropy of the degeneracy distribution - Collision count: number of sum values that appear ≥ 2 times Each computation step is a DAG node, checkpointed to disk for resume. The DAG structure allows adversarial review of each step. """ import sys import math import json import time import hashlib import itertools from pathlib import Path from collections import Counter REPO_ROOT = Path(__file__).resolve().parent.parent ARTIFACTS_DIR = REPO_ROOT / ".openresearch" / "artifacts" DAG_PATH = ARTIFACTS_DIR / "prime_sidon_dag.json" OUTPUT_PATH = ARTIFACTS_DIR / "prime_sidon_explore.json" # ── Recoverable DAG ────────────────────────────────────────────────────────── class ComputationDAG: def __init__(self): self.nodes = [] self.edges = [] ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True) def add_node(self, node_id, node_type, inputs, result, status, elapsed): node = { "id": node_id, "type": node_type, "inputs": inputs, "result": result, "status": status, "elapsed_s": round(elapsed, 4), "timestamp": time.time(), } self.nodes.append(node) return node def add_edge(self, from_id, to_id, edge_type="depends_on"): self.edges.append({"from": from_id, "to": to_id, "type": edge_type}) def has_node(self, node_id): return any(n["id"] == node_id for n in self.nodes) def get_node(self, node_id): for n in self.nodes: if n["id"] == node_id: return n["result"] return None def save(self): dag_state = {"nodes": self.nodes, "edges": self.edges} DAG_PATH.write_text(json.dumps(dag_state, default=str, indent=2)) def save_report(self): report_path = ARTIFACTS_DIR / "prime_sidon_dag.md" lines = [ "# Prime Sidon Exploration DAG\n", f"**Nodes:** {len(self.nodes)}", f"**Edges:** {len(self.edges)}\n", "## Nodes\n", "| ID | Type | Status | Elapsed | Inputs |", "|----|------|--------|---------|--------|", ] for n in self.nodes: inp = json.dumps(n["inputs"], default=str)[:60] lines.append(f"| {n['id']} | {n['type']} | {n['status']} | {n['elapsed_s']}s | {inp} |") lines.append("\n## Edges\n| From | To | Type |\n|------|----|------|") for e in self.edges: lines.append(f"| {e['from']} | {e['to']} | {e['type']} |") report_path.write_text("\n".join(lines)) return report_path # ── Prime generation (integer only) ────────────────────────────────────────── def primes_upto(limit): """Segmented sieve. Pure integer arithmetic.""" if limit < 2: return [] sieve = bytearray(b'\x01') * (limit + 1) sieve[0:2] = b'\x00\x00' for i in range(2, int(limit ** 0.5) + 1): if sieve[i]: step = i start = i * i sieve[start:limit+1:step] = b'\x00' * ((limit - start) // step + 1) return [i for i, is_prime in enumerate(sieve) if is_prime] def prime_cluster_via_sieve(count, offset_start): """Get count consecutive primes starting at offset_start-th prime via sieve.""" limit = max(offset_start * 2 + 100, 10_000_000) all_primes = primes_upto(limit) if offset_start + count <= len(all_primes): return all_primes[offset_start:offset_start + count] return None def prime_cluster_via_sympy(start_val, count): """Get count consecutive primes starting from start_val via sympy.nextprime.""" import sympy p = sympy.nextprime(start_val) cluster = [] for _ in range(count): cluster.append(p) p = sympy.nextprime(p) return cluster # ── Reference label sets ───────────────────────────────────────────────────── def sidon_pow2_set(size): """Powers of 2: perfect Sidon (all pairwise sums distinct).""" return [1 << i for i in range(size)] def nonsidon_consecutive_set(size, start=1): """Consecutive integers: maximally non-Sidon for a given size.""" return list(range(start, start + size)) # ── Integer-only Sidon sum-degeneracy metrics ─────────────────────────────── def sidon_metrics(labels): """Compute Sidon sum-degeneracy metrics using only integer arithmetic. All sums a_i + a_j are computed as exact integers. No floating point. The sum matrix M[i][j] = labels[i] + labels[j] has n^2 entries. Returns: n: number of labels total_pairs: n^2 distinct_sums: number of unique sum values sidon_score: distinct_sums / n^2 (1.0 = perfect Sidon) max_degeneracy: max multiplicity of any single sum collision_count: number of sums with multiplicity >= 2 collision_entropy: Shannon entropy of degeneracy distribution sum_histogram: {sum_value: count} for sums with count >= 2 """ n = len(labels) total_pairs = n * n # Count occurrences of each sum (pure integer arithmetic) sum_counts = Counter() for i in range(n): for j in range(n): sum_counts[labels[i] + labels[j]] += 1 distinct_sums = len(sum_counts) sidon_score = distinct_sums / total_pairs # Degeneracy analysis multiplicities = list(sum_counts.values()) max_degeneracy = max(multiplicities) if multiplicities else 0 collision_count = sum(1 for c in multiplicities if c >= 2) # Shannon entropy of degeneracy distribution probs = [c / total_pairs for c in multiplicities] collision_entropy = -sum(p * math.log2(p) for p in probs if p > 0) # Report only collisions (sums with count >= 2) collision_histogram = {str(k): v for k, v in sum_counts.items() if v >= 2} return { "n": n, "total_pairs": total_pairs, "distinct_sums": distinct_sums, "sidon_score": round(sidon_score, 6), "max_degeneracy": max_degeneracy, "collision_count": collision_count, "collision_entropy": round(collision_entropy, 4), "collision_histogram": collision_histogram, } # ── Permutation test (null: random distinct integers at same scale) ───────── def random_sidon_score(size, range_min, range_max, trials=1000, rng=None): """Generate `trials` random n-subsets of distinct integers in [min, max] and return the distribution of Sidon scores under the null hypothesis.""" if rng is None: rng = __import__("random").Random(42) scores = [] for _ in range(trials): sample = sorted(rng.sample(range(range_min, range_max + 1), size)) scores.append(sidon_metrics(sample)["sidon_score"]) scores.sort() return { "mean": round(sum(scores) / len(scores), 4), "std": round((sum((s - sum(scores)/len(scores))**2 for s in scores) / len(scores))**0.5, 4), "p5": round(scores[int(len(scores) * 0.05)], 4), "p50": round(scores[int(len(scores) * 0.50)], 4), "p95": round(scores[int(len(scores) * 0.95)], 4), "trials": trials, } # ── Main exploration ──────────────────────────────────────────────────────── def explore(): print("=" * 60) print(" Prime Sidon Sum-Degeneracy Exploration") print(" Integer-only arithmetic. Permutation test vs random null.") print("=" * 60) dag = ComputationDAG() # Scale definitions scales = { "small": {"offset": 0, "label": "primes 2..19"}, "kilo": {"offset": 168, "label": "primes near 1000"}, "million": {"offset": 78498, "label": "primes near 10^6"}, } large_scales = { "billion": 10**9, "trillion": 10**12, "quadrillion": 10**15, "quintillion": 10**18, } sizes = [5, 6, 7, 8, 10] results = [] N_TRIALS = 1000 # ── Step 1: Analytical bounds ───────────────────────────────────────── print("\n--- Step 1: Analytical Sidon bounds ---") print(" For any n distinct integers:") print(" Lower bound (consecutive): (2n-1)/n²") print(" Upper bound (perfect Sidon): (n+1)/(2n)") print(" The 'BETWEEN' claim is a tautology — testing if primes") print(" deviate from random expectation within these bounds.") print() bound_nodes = {} for size in sizes: lower = round((2 * size - 1) / (size * size), 4) upper = round((size + 1) / (2 * size), 4) node_id = f"bounds_s{size}" dag.add_node(node_id, "analytical_bounds", {"size": size, "lower_bound": lower, "upper_bound": upper}, {"lower": lower, "upper": upper}, "success", 0.0) bound_nodes[size] = {"lower": lower, "upper": upper} print(f" size={size}: lower={lower:.4f} upper={upper:.4f}") # ── Step 2: Reference sets (baseline verification) ──────────────────── print("\n--- Step 2: Reference baseline verification ---") for size in sizes: labels = sidon_pow2_set(size) metrics = sidon_metrics(labels) node_id = f"sidon_pow2_s{size}" dag.add_node(node_id, "reference", {"type": "sidon_pow2", "size": size, "labels": labels}, metrics, "success", 0.0) bound = bound_nodes[size] at_upper = abs(metrics["sidon_score"] - bound["upper"]) < 1e-6 print(f" {node_id:25s} score={metrics['sidon_score']:.4f} (upper={bound['upper']:.4f}, at_bound={at_upper})") labels = nonsidon_consecutive_set(size) metrics = sidon_metrics(labels) node_id = f"nonsidon_seq_s{size}" dag.add_node(node_id, "reference", {"type": "nonsidon_consecutive", "size": size, "labels": labels}, metrics, "success", 0.0) at_lower = abs(metrics["sidon_score"] - bound["lower"]) < 1e-6 print(f" {node_id:25s} score={metrics['sidon_score']:.4f} (lower={bound['lower']:.4f}, at_bound={at_lower})") # ── Step 3: Prime sets with permutation test ────────────────────────── print("\n--- Step 3: Prime sets vs random null ---") import random as _random rng = _random.Random(42) for scale_name, scale_info in scales.items(): offset = scale_info["offset"] for size in sizes: cluster = prime_cluster_via_sieve(size, offset) if cluster is None: cluster = prime_cluster_via_sympy(2, offset + size) cluster = cluster[offset:offset + size] node_id = f"primes_{scale_name}_s{size}" t0 = time.time() metrics = sidon_metrics(cluster) # Permutation test: random n-subsets in [min(cluster), max(cluster)] range_min = min(cluster) range_max = max(cluster) null_dist = random_sidon_score(size, range_min, range_max, N_TRIALS, rng) # p-value: proper permutation test null_scores = [] for _ in range(N_TRIALS): sample = sorted(rng.sample(range(range_min, range_max + 1), size)) null_scores.append(sidon_metrics(sample)["sidon_score"]) null_scores.sort() p_less = sum(1 for s in null_scores if s <= metrics["sidon_score"]) / N_TRIALS p_greater = sum(1 for s in null_scores if s >= metrics["sidon_score"]) / N_TRIALS p_two_sided = min(1.0, 2 * min(p_less, p_greater)) effect = round(metrics["sidon_score"] - null_dist["mean"], 4) significant = p_two_sided < 0.05 elapsed = time.time() - t0 dag.add_node(node_id, "prime_set", {"size": size, "scale": scale_name, "labels": cluster, "range": [range_min, range_max], "null_trials": N_TRIALS}, {**metrics, "null_distribution": null_dist, "p_value_two_sided": round(p_two_sided, 4), "effect_size": effect, "significant": significant}, "success", elapsed) results.append({ "desc": node_id, "scale": scale_name, "n_labels": size, "labels": cluster, "metrics": metrics, "null_distribution": null_dist, "p_value": round(p_two_sided, 4), "effect_size": effect, "significant": significant, }) sig_str = "***" if significant else "" dir_str = "LESS Sidon-like" if effect < 0 else "MORE Sidon-like" print(f" {node_id:30s} score={metrics['sidon_score']:.4f} " f"null={null_dist['mean']:.4f}±{null_dist['std']:.4f} " f"Δ={effect:+.4f} p={p_two_sided:.4f} {sig_str} ({dir_str})") # ── Step 4: Large prime scales (via sympy.nextprime) ────────────────── print("\n--- Step 4: Large prime scales vs random null ---") for scale_name, start_val in large_scales.items(): try: import sympy except ImportError: print(f" sympy not available, skipping {scale_name}") continue for size in sizes: node_id = f"primes_{scale_name}_s{size}" t0 = time.time() cluster = prime_cluster_via_sympy(start_val, size) metrics = sidon_metrics(cluster) range_min = min(cluster) range_max = max(cluster) null_dist = random_sidon_score(size, range_min, range_max, N_TRIALS, rng) null_scores = [] for _ in range(N_TRIALS): sample = sorted(rng.sample(range(range_min, range_max + 1), size)) null_scores.append(sidon_metrics(sample)["sidon_score"]) null_scores.sort() p_less = sum(1 for s in null_scores if s <= metrics["sidon_score"]) / N_TRIALS p_greater = sum(1 for s in null_scores if s >= metrics["sidon_score"]) / N_TRIALS p_two_sided = min(1.0, 2 * min(p_less, p_greater)) effect = round(metrics["sidon_score"] - null_dist["mean"], 4) significant = p_two_sided < 0.05 elapsed = time.time() - t0 dag.add_node(node_id, "prime_set", {"size": size, "scale": scale_name, "labels": cluster, "range": [range_min, range_max], "null_trials": N_TRIALS}, {**metrics, "null_distribution": null_dist, "p_value_two_sided": round(p_two_sided, 4), "effect_size": effect, "significant": significant}, "success", elapsed) results.append({ "desc": node_id, "scale": scale_name, "n_labels": size, "labels": cluster, "metrics": metrics, "null_distribution": null_dist, "p_value": round(p_two_sided, 4), "effect_size": effect, "significant": significant, }) sig_str = "***" if significant else "" dir_str = "LESS Sidon-like" if effect < 0 else "MORE Sidon-like" print(f" {node_id:30s} score={metrics['sidon_score']:.4f} " f"null={null_dist['mean']:.4f}±{null_dist['std']:.4f} " f"Δ={effect:+.4f} p={p_two_sided:.4f} {sig_str} ({dir_str})") # ── Step 5: Cross-scale analysis ────────────────────────────────────── print("\n--- Step 5: Cross-scale analysis ---") analysis = [] for size in sizes: size_results = [r for r in results if r["n_labels"] == size] # How many are significant? n_sig = sum(1 for r in size_results if r["significant"]) n_total = len(size_results) all_sig = n_sig == n_total # Direction of effect neg_effects = sum(1 for r in size_results if r["effect_size"] < 0) pos_effects = sum(1 for r in size_results if r["effect_size"] > 0) # Score trend with scale scores_by_scale = {r["scale"]: r["metrics"]["sidon_score"] for r in size_results} ascending_scales = [s for s in ["small", "kilo", "million", "billion", "trillion", "quadrillion", "quintillion"] if s in scores_by_scale] score_trend = [scores_by_scale[s] for s in ascending_scales] increasing = all(score_trend[i] <= score_trend[i+1] for i in range(len(score_trend)-1)) analysis.append({ "size": size, "n_significant": n_sig, "n_total": n_total, "all_significant": all_sig, "negative_effects": neg_effects, "positive_effects": pos_effects, "score_trend_increasing": increasing, "scores_by_scale": scores_by_scale, }) print(f" size={size}: {n_sig}/{n_total} significant, " f"{neg_effects} less Sidon-like, {pos_effects} more Sidon-like, " f"trend={'↑' if increasing else '~'}") # ── Step 6: Summary ───────────────────────────────────────────────── print("\n--- Summary ---") total_sig = sum(1 for r in results if r["significant"]) total_cases = len(results) print(f" Total test cases: {total_cases}") print(f" Significant at p<0.05: {total_sig}/{total_cases}") print() print(f" Key question: are prime Sidon scores different from random") print(f" n-subsets of integers at the same scale?") print(f" Answer: {total_sig}/{total_cases} cases show significant deviation.") # Summary node summary_node = { "schema": "prime_sidon_explore_v2", "claim_boundary": "prime-sidon-vs-random-null:permutation-test", "config": {"sizes": sizes, "scales": list(scales.keys()) + list(large_scales.keys()), "null_trials": N_TRIALS}, "analysis": analysis, "total_test_cases": total_cases, "total_significant": total_sig, } dag.add_node("summary", "summary", summary_node, summary_node, "success", 0.0) for r in results: dag.add_edge(f"primes_{r['scale']}_s{r['n_labels']}", "summary", "feeds_summary") # ── Save ────────────────────────────────────────────────────────────── dag.save() report_path = dag.save_report() with open(OUTPUT_PATH, "w") as f: json.dump(summary_node, f, indent=2, default=str) print(f"\n DAG nodes: {len(dag.nodes)}") print(f" DAG edges: {len(dag.edges)}") print(f" DAG: {DAG_PATH}") print(f" Report: {report_path}") print("=" * 60) if __name__ == "__main__": explore()