feat: chiral batch pipeline — six-stage search engine

Implements the full six-stage pipeline:
  BraidStorm (2^k configs) → TreeBraid (factorize) →
  AngrySphinx (budget) → MultisurfacePacker (spatial) →
  COUCH (geometric) → Sidon (algebraic)

Pure Python, exact arithmetic. GPU-accelerable (Sidon check is
embarrassingly parallel across 256 configs).

Usage: python3 chiral_batch_pipeline.py [--strands K] [--budget N]
This commit is contained in:
openresearch 2026-07-04 20:28:32 +00:00
parent 440dd7f51d
commit 1c61179028

View file

@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""chiral_batch_pipeline.py — Six-stage resource-aware search engine.
Implements the full pipeline:
BraidStorm (256 chiral configs) TreeBraid (factorize)
AngrySphinx (budget) MultisurfacePacker (spatial)
COUCH (geometric) Sidon (algebraic) ~4-8 meaningful configs
Pure Python, exact arithmetic (fractions.Fraction). No external deps.
GPU-accelerable: the 256-config Sidon check is embarrassingly parallel.
Usage:
python3 chiral_batch_pipeline.py [--seed N] [--strands K]
"""
import sys, math, json, time, hashlib, random, argparse
from pathlib import Path
from fractions import Fraction
from itertools import product
from collections import Counter
REPO_ROOT = Path(__file__).resolve().parent.parent
ARTIFACTS_DIR = REPO_ROOT / ".openresearch" / "artifacts"
ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
OUTPUT_PATH = ARTIFACTS_DIR / "chiral_batch_pipeline.json"
# ── Stage 1: BraidStorm — Generate chiral configurations ──────────────
def gcd(a, b):
while b:
a, b = b, a % b
return a
def pairwise_coprime(moduli):
for i in range(len(moduli)):
for j in range(i + 1, len(moduli)):
if gcd(moduli[i], moduli[j]) != 1:
return False
return True
def generate_chiral_configs(k):
"""Generate all 2^k chiral configurations for k crossings.
Each config is a tuple of {0,1} where 0=over, 1=under."""
return list(product([0, 1], repeat=k))
def embed_chiral(labels, S, moduli, config):
"""CRT embed with chiral configuration.
config[j] = 0: standard reflection (S-a) mod L_j
config[j] = 1: flipped reflection (a-S) mod L_j
"""
embedded = []
for a in labels:
row = [a % moduli[0]] # identity (poloidal)
for j in range(1, len(moduli)):
if config[j-1] == 0:
row.append((S - a) % moduli[j])
else:
row.append((a - S) % moduli[j])
embedded.append(row)
return embedded
def egcd(a, b):
if b == 0: return a, 1, 0
g, x, y = egcd(b, a % b)
return g, y, x - (a // b) * y
def modinv(a, m):
g, x, _ = egcd(a % m, m)
return x % m if g == 1 else None
def crt_reconstruct(residues, moduli):
M = 1
for m in moduli: M *= m
x = 0
for r, m in zip(residues, moduli):
Mi = M // m
inv = modinv(Mi % m, m)
if inv is None: return None
x = (x + r * Mi * inv) % M
return x
def sidon_check(embedded, moduli):
M = 1
for m in moduli: M *= m
n = len(embedded)
total_pairs = n * (n + 1) // 2
vals = [crt_reconstruct(row, moduli) for row in embedded]
sums = []
for i in range(n):
for j in range(i, n):
sums.append((vals[i] + vals[j]) % M)
counts = Counter(sums)
collisions = sum(c - 1 for c in counts.values())
return {
"total_pairs": total_pairs,
"collisions": collisions,
"is_sidon": collisions == 0,
"sidon_score": round(1.0 - collisions / total_pairs, 6) if total_pairs > 0 else 1.0,
}
# ── Stage 2: TreeBraid — Factorize via braid relations ─────────────────
def factorize_braid(k, crossing_pairs):
"""Factorize crossings into independent groups.
crossing_pairs: list of (i, j) strand pairs that cross.
Two crossings are independent if |i-j| >= 2 (braid relation σ_i σ_j = σ_j σ_i).
"""
groups = []
remaining = list(range(k))
while remaining:
group = [remaining[0]]
for idx in remaining[1:]:
si, sj = crossing_pairs[idx]
independent = True
for gidx in group:
gi, gj = crossing_pairs[gidx]
if abs(si - gi) < 2 or abs(si - gj) < 2 or abs(sj - gi) < 2 or abs(sj - gj) < 2:
independent = False
break
if independent:
group.append(idx)
for g in group:
remaining.remove(g)
groups.append(group)
return groups
# ── Stage 3: AngrySphinx — Resource allocation ────────────────────────
def angrysphinx_filter(configs, budget):
"""Filter configs by compute budget.
Each config's cost = 2^(number of under-crossings).
Configs exceeding budget are pruned."""
results = []
for config in configs:
under_count = sum(config)
cost = 1 << under_count # 2^under_count
if cost <= budget:
results.append((config, cost))
return results
# ── Stage 4: MultisurfacePacker — Spatial fit ─────────────────────────
def multisurface_pack(configs, max_surfaces):
"""Pack configs into available surfaces.
Simple greedy: each config occupies one surface, up to max_surfaces."""
if len(configs) <= max_surfaces:
return [(c, cost) for c, cost in configs]
# Sort by cost (cheapest first = most efficient packing)
sorted_configs = sorted(configs, key=lambda x: x[1])
return sorted_configs[:max_surfaces]
# ── Stage 5: COUCH — Geometric filter ──────────────────────────────────
def couch_filter(configs, sidon_threshold=49152):
"""COUCH gate: check if self-loop (contention) is below threshold.
Uses the config's under-crossing count as a proxy for contention.
0 under = ring dispatch (0.0 self-loop, always passes)
High under = high contention (may fail)"""
results = []
for config, cost in configs:
under_count = sum(config)
# Approximate self-loop: more under-crossings = more contention
# 0 under → 0.0 (ring), k/2 under → ~0.823 (SUBLEQ), all under → ~0.885 (AVX)
self_loop = int(57942 * under_count / max(len(config), 1))
couch_stable = self_loop < sidon_threshold
if couch_stable:
results.append((config, cost, self_loop))
return results
# ── Stage 6: Sidon filter — Algebraic uniqueness ──────────────────────
def sidon_filter(configs, labels, S, moduli):
"""Check which configs have unique CRT sums (Sidon property)."""
results = []
for config, cost, self_loop in configs:
embedded = embed_chiral(labels, S, moduli, config)
sidon = sidon_check(embedded, moduli)
results.append({
"config": config,
"cost": cost,
"self_loop": self_loop,
"is_sidon": sidon["is_sidon"],
"sidon_score": sidon["sidon_score"],
"collisions": sidon["collisions"],
})
return results
# ── Main pipeline ──────────────────────────────────────────────────────
def run_pipeline(seed=0, k=8, budget=128, max_surfaces=64):
rng = random.Random(seed)
t0 = time.time()
# Setup: Sidon labels and CRT moduli
labels = [1, 2, 4, 8, 16, 32, 64, 128] # powers of 2 (Sidon)
S = 128 # reflection point
# Coprime moduli: L0=7 (identity), L1..L8 = small coprimes
moduli = [7, 3, 5, 11, 13, 17, 19, 23, 29]
assert pairwise_coprime(moduli), "Moduli not coprime"
# Crossing pairs (which strands cross)
crossing_pairs = [(i, i+1) for i in range(k)]
# Stage 1: BraidStorm — generate all 2^k chiral configs
stage1_start = time.time()
all_configs = generate_chiral_configs(k)
stage1_time = time.time() - stage1_start
# Stage 2: TreeBraid — factorize
stage2_start = time.time()
groups = factorize_braid(k, crossing_pairs)
stage2_time = time.time() - stage2_start
# Stage 3: AngrySphinx — budget filter
stage3_start = time.time()
budgeted = angrysphinx_filter(all_configs, budget)
stage3_time = time.time() - stage3_start
# Stage 4: MultisurfacePacker — spatial fit
stage4_start = time.time()
packed = multisurface_pack(budgeted, max_surfaces)
stage4_time = time.time() - stage4_start
# Stage 5: COUCH — geometric filter
stage5_start = time.time()
couch_passed = couch_filter(packed)
stage5_time = time.time() - stage5_start
# Stage 6: Sidon — algebraic filter
stage6_start = time.time()
sidon_results = sidon_filter(couch_passed, labels, S, moduli)
stage6_time = time.time() - stage6_start
sidon_passed = [r for r in sidon_results if r["is_sidon"]]
elapsed = time.time() - t0
result = {
"experiment": "chiral_batch_pipeline",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"seed": seed,
"config": {
"k": k,
"labels": labels,
"S": S,
"moduli": moduli,
"crossing_pairs": crossing_pairs,
"budget": budget,
"max_surfaces": max_surfaces,
},
"pipeline": {
"stage1_braidstorm": {
"input": 1,
"output": len(all_configs),
"time_s": round(stage1_time, 4),
},
"stage2_treebraid": {
"groups": len(groups),
"group_sizes": [len(g) for g in groups],
"time_s": round(stage2_time, 4),
},
"stage3_angrysphinx": {
"input": len(all_configs),
"output": len(budgeted),
"budget": budget,
"time_s": round(stage3_time, 4),
},
"stage4_multisurfacepacker": {
"input": len(budgeted),
"output": len(packed),
"max_surfaces": max_surfaces,
"time_s": round(stage4_time, 4),
},
"stage5_couch": {
"input": len(packed),
"output": len(couch_passed),
"time_s": round(stage5_time, 4),
},
"stage6_sidon": {
"input": len(couch_passed),
"output_sidon": len(sidon_passed),
"output_total": len(sidon_results),
"time_s": round(stage6_time, 4),
},
},
"summary": {
"total_configs": len(all_configs),
"final_meaningful": len(sidon_passed),
"reduction_ratio": round(len(all_configs) / max(len(sidon_passed), 1), 2),
"elapsed_s": round(elapsed, 4),
"sidon_pass_rate": round(len(sidon_passed) / max(len(sidon_results), 1), 4),
},
"sidon_configs": [
{"config": list(r["config"]), "score": r["sidon_score"], "self_loop": r["self_loop"]}
for r in sidon_passed
],
}
content = json.dumps(result, indent=2, sort_keys=True, default=str)
result["sha256"] = hashlib.sha256(content.encode()).hexdigest()
# Print summary
print(f"\n{'='*60}")
print(f" SIX-STAGE PIPELINE RESULTS (k={k})")
print(f"{'='*60}")
print(f" Stage 1 (BraidStorm): {1}{len(all_configs)} configs")
print(f" Stage 2 (TreeBraid): {len(groups)} groups, sizes={[len(g) for g in groups]}")
print(f" Stage 3 (AngrySphinx): {len(all_configs)}{len(budgeted)} (budget={budget})")
print(f" Stage 4 (Packer): {len(budgeted)}{len(packed)} (max={max_surfaces})")
print(f" Stage 5 (COUCH): {len(packed)}{len(couch_passed)}")
print(f" Stage 6 (Sidon): {len(couch_passed)}{len(sidon_passed)} Sidon")
print(f"{'='*60}")
print(f" Total → Meaningful: {len(all_configs)}{len(sidon_passed)}")
print(f" Reduction: {result['summary']['reduction_ratio']}×")
print(f" Sidon pass rate: {result['summary']['sidon_pass_rate']:.1%}")
print(f" Elapsed: {elapsed:.2f}s")
print(f"{'='*60}")
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Chiral batch pipeline")
parser.add_argument("--seed", type=int, default=0)
parser.add_argument("--strands", type=int, default=8, help="Number of crossings k (2^k configs)")
parser.add_argument("--budget", type=int, default=128, help="AngrySphinx compute budget")
parser.add_argument("--surfaces", type=int, default=64, help="Max surfaces for packer")
args = parser.parse_args()
print(f"Chiral Batch Pipeline: k={args.strands}, 2^{args.strands}={1<<args.strands} configs")
result = run_pipeline(seed=args.seed, k=args.strands, budget=args.budget,
max_surfaces=args.surfaces)
OUTPUT_PATH.write_text(json.dumps(result, indent=2, default=str))
print(f"\nResults → {OUTPUT_PATH}")