mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
302 lines
10 KiB
Python
302 lines
10 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
braid_photonic_emulator.py
|
||
Photonic emulation bridge for the Braid-Neuromorphic Genetic Ladder.
|
||
|
||
Maps integer-shell braid events to a Simphony photonic circuit netlist.
|
||
Each event becomes a Mach-Zehnder Interferometer (MZI) stage:
|
||
y-branch splitter -> phase-shifted arm + reference arm -> y-branch combiner
|
||
The phase shift encodes timing_phase, and the arm-length difference encodes
|
||
polarity/interaction. The resulting S21 spectrum gives a continuous-domain
|
||
view of the standing-wave field and genetic ladder topology.
|
||
|
||
Requires: simphony, sax, jax, matplotlib, numpy
|
||
Run with: source .venv-simphony/bin/activate && python 5-Applications/tools-5-Applications/scripts/braid_photonic_emulator.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
import json
|
||
import argparse
|
||
import functools
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
import sax
|
||
from jax import config
|
||
|
||
config.update("jax_enable_x64", True)
|
||
|
||
from simphony.libraries import siepic
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Braid logic (mirrors braid_field_builder.py)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Braid logic (mirrors braid_field_builder.py)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass(frozen=True)
|
||
class BraidEventParams:
|
||
"""Single source of truth for every magic number in compute_event.
|
||
Factored per 5-Applications/tools-scripts/formula_optimization/braid_event_delta_gcl.py"""
|
||
decay_base: float = 0.5 # tail/echo geometric decay base
|
||
tail_depth: int = 3 # tail_weights has 3 entries
|
||
echo_depth: int = 2 # Fm uses decay^1, Fp uses decay^2
|
||
phase_tanh_coef: float = 2.0 # tanh contribution to phase
|
||
phase_tanh_scale: float = 64.0 # interaction scale inside tanh
|
||
phase_clip_range: int = 3 # = phase_linear_coef (the redundancy)
|
||
|
||
@property
|
||
def tail_weights(self) -> dict[int, float]:
|
||
return {k: -(self.decay_base ** (k - 1)) for k in range(1, self.tail_depth + 1)}
|
||
|
||
@property
|
||
def echo_coefs(self) -> tuple[float, float]:
|
||
# (Fm uses ^1, Fp uses ^2)
|
||
return (self.decay_base ** 1, self.decay_base ** 2)
|
||
|
||
@property
|
||
def phase_linear_coef(self) -> int:
|
||
return self.phase_clip_range
|
||
|
||
|
||
# Canonical Fc table reframed via sign × magnitude
|
||
_PURINES = {"A", "G"}
|
||
_CANONICAL = {"A", "T"} # full magnitude (1.0)
|
||
_WOBBLE = {"G", "C"} # half magnitude (0.5)
|
||
|
||
|
||
def _Fc_canonical(et: str) -> float:
|
||
sign = +1.0 if et in _PURINES else -1.0
|
||
magnitude = 1.0 if et in _CANONICAL else 0.5
|
||
return sign * magnitude
|
||
|
||
|
||
def shell_state(n: int):
|
||
k = int(math.isqrt(n))
|
||
a = n - k * k
|
||
b = (k + 1) * (k + 1) - n
|
||
return {"n": n, "k": k, "a": a, "b": b, "width": 2 * k + 1}
|
||
|
||
|
||
def classify_event(s: dict):
|
||
k, n = s["k"], s["n"]
|
||
if n == k * k:
|
||
return "A"
|
||
if n == k * k + k:
|
||
return "G"
|
||
if n == k * k + k + 1:
|
||
return "C"
|
||
if n == (k + 1) * (k + 1) - 1:
|
||
return "T"
|
||
return None
|
||
|
||
|
||
def compute_event(n: int, params: BraidEventParams = BraidEventParams()):
|
||
s = shell_state(n)
|
||
et = classify_event(s)
|
||
if et is None:
|
||
return None
|
||
a, b, k = s["a"], s["b"], s["k"]
|
||
mass = a * b
|
||
polarity = a - b
|
||
shell_width = 2 * k + 1
|
||
|
||
# Standing-wave echo (anti-causal tail approximation)
|
||
echo = 0.0
|
||
for tail, weight in params.tail_weights.items():
|
||
if n - tail >= 0:
|
||
prev = shell_state(n - tail)
|
||
prev_et = classify_event(prev)
|
||
if prev_et is not None:
|
||
echo += weight * prev["a"] * prev["b"]
|
||
|
||
# Field channels
|
||
cm, cp = params.echo_coefs
|
||
Fm = mass + cm * echo
|
||
Fp = polarity + cp * echo
|
||
Fc = _Fc_canonical(et)
|
||
|
||
interaction = mass * Fm + polarity * Fp + Fc
|
||
|
||
R = params.phase_clip_range
|
||
phase = max(-R, min(R, round(
|
||
params.phase_linear_coef * polarity / shell_width
|
||
+ params.phase_tanh_coef * math.tanh(interaction / params.phase_tanh_scale)
|
||
)))
|
||
index_bit = 1 if interaction > 0 else 0
|
||
|
||
return {
|
||
"n": n,
|
||
"k": k,
|
||
"et": et,
|
||
"mass": mass,
|
||
"polarity": polarity,
|
||
"shell_width": shell_width,
|
||
"echo": echo,
|
||
"Fm": Fm,
|
||
"Fp": Fp,
|
||
"Fc": Fc,
|
||
"interaction": interaction,
|
||
"phase": int(phase),
|
||
"index_bit": index_bit,
|
||
}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Photonic model wrappers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _phase_waveguide(wl: float = 1.55, length: float = 0.0, phase: float = 0.0):
|
||
"""Ideal waveguide with an additional phase shift."""
|
||
wg = siepic.waveguide(wl=wl, length=length)
|
||
j = complex(0, 1)
|
||
rot = cmath.exp(j * phase)
|
||
wg_rot = {}
|
||
for (p1, p2), val in wg.items():
|
||
if (p1 == "o0" and p2 == "o1") or (p1 == "o1" and p2 == "o0"):
|
||
wg_rot[(p1, p2)] = val * rot
|
||
else:
|
||
wg_rot[(p1, p2)] = val
|
||
return wg_rot
|
||
|
||
|
||
def _ybranch(wl: float = 1.55):
|
||
return siepic.y_branch(wl=wl)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Photonic netlist builder
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def build_mzi_ladder_netlist(events: list[dict]):
|
||
"""
|
||
Build a cascade of MZIs, one per event.
|
||
Each MZI: y-split_i -> wgA_i -> y-comb_i
|
||
-> wgB_i ->
|
||
"""
|
||
instances: dict[str, callable] = {}
|
||
connections: dict[str, str] = {}
|
||
|
||
for i, ev in enumerate(events):
|
||
# Map braid features to photonic parameters
|
||
base_length = 50e-6 # 50 µm base arm
|
||
delta_length = ev["polarity"] * 2e-6 # ±2 µm per polarity unit
|
||
phase_rad = math.radians(ev["phase"] * 15.0) # ±3 -> ±45°
|
||
|
||
split_name = f"split_{i}"
|
||
comb_name = f"comb_{i}"
|
||
wgA_name = f"wgA_{i}"
|
||
wgB_name = f"wgB_{i}"
|
||
|
||
instances[split_name] = functools.partial(_ybranch)
|
||
instances[comb_name] = functools.partial(_ybranch)
|
||
instances[wgA_name] = functools.partial(_phase_waveguide, length=base_length + delta_length, phase=phase_rad)
|
||
instances[wgB_name] = functools.partial(_phase_waveguide, length=base_length - delta_length, phase=0.0)
|
||
|
||
# Internal MZI wiring
|
||
connections[f"{split_name},port_2"] = f"{wgA_name},o0"
|
||
connections[f"{split_name},port_3"] = f"{wgB_name},o0"
|
||
connections[f"{wgA_name},o1"] = f"{comb_name},port_2"
|
||
connections[f"{wgB_name},o1"] = f"{comb_name},port_3"
|
||
|
||
# Chain to next stage
|
||
if i + 1 < len(events):
|
||
connections[f"{comb_name},port_1"] = f"split_{i+1},port_1"
|
||
|
||
ports = {"in": "split_0,port_1", "out": f"comb_{len(events)-1},port_1"}
|
||
|
||
return {"instances": instances, "connections": connections, "ports": ports}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Simulation runner
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def run_photonic_simulation(events: list[dict], wl_start: float = 1.50, wl_stop: float = 1.60, points: int = 1001):
|
||
netlist = build_mzi_ladder_netlist(events)
|
||
circuit, info = sax.circuit(netlist, models={})
|
||
wl = np.linspace(wl_start, wl_stop, points)
|
||
s_params = circuit(wl=wl)
|
||
s21 = s_params[("in", "out")]
|
||
transmission = np.abs(s21) ** 2
|
||
return wl, transmission, events
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# CLI
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Photonic emulation of the Braid-Neuromorphic Genetic Ladder")
|
||
parser.add_argument("--start", type=int, default=1, help="Start integer n")
|
||
parser.add_argument("--stop", type=int, default=200, help="Stop integer n (inclusive)")
|
||
parser.add_argument("--csv", type=str, default="braid_photonic_emulation.csv", help="Output CSV path")
|
||
parser.add_argument("--png", type=str, default="braid_photonic_emulation.png", help="Output plot path")
|
||
parser.add_argument("--json", type=str, default="braid_photonic_netlist.json", help="Output netlist JSON")
|
||
args = parser.parse_args()
|
||
|
||
events = [ev for n in range(args.start, args.stop + 1) if (ev := compute_event(n)) is not None]
|
||
print(f"Computed {len(events)} axial events for n={args.start}..{args.stop}")
|
||
|
||
if not events:
|
||
print("No events to simulate.")
|
||
return
|
||
|
||
print("Building photonic MZI ladder netlist ...")
|
||
wl, transmission, events = run_photonic_simulation(events)
|
||
print(f"Simulated {len(wl)} wavelength points.")
|
||
|
||
# CSV export (one row per event with center-wavelength transmission)
|
||
out_csv = Path(args.csv)
|
||
with out_csv.open("w") as f:
|
||
f.write("n,k,event,mass,polarity,interaction,phase,index_bit,wl_um,transmission_dB\n")
|
||
center_idx = len(wl) // 2
|
||
for ev in events:
|
||
t = transmission[center_idx]
|
||
t_db = 10.0 * math.log10(max(1e-12, t))
|
||
f.write(
|
||
f"{ev['n']},{ev['k']},{ev['et']},"
|
||
f"{ev['mass']},{ev['polarity']},{ev['interaction']:.6f},"
|
||
f"{ev['phase']},{ev['index_bit']},"
|
||
f"{wl[center_idx]:.6f},{t_db:.6f}\n"
|
||
)
|
||
print(f"Wrote CSV: {out_csv.resolve()}")
|
||
|
||
# Plot
|
||
fig, ax = plt.subplots(figsize=(10, 5))
|
||
ax.plot(wl, 10 * np.log10(np.maximum(1e-12, transmission)), label="S21 (dB)")
|
||
ax.set_xlabel("Wavelength (µm)")
|
||
ax.set_ylabel("Transmission (dB)")
|
||
ax.set_title("Braid-Neuromorphic Genetic Ladder — Photonic MZI Emulation")
|
||
ax.legend()
|
||
ax.grid(True)
|
||
fig.tight_layout()
|
||
out_png = Path(args.png)
|
||
fig.savefig(out_png, dpi=150)
|
||
print(f"Wrote plot: {out_png.resolve()}")
|
||
|
||
# Netlist JSON (metadata only)
|
||
netlist = build_mzi_ladder_netlist(events)
|
||
out_json = Path(args.json)
|
||
with out_json.open("w") as f:
|
||
json.dump(
|
||
{
|
||
"instances": list(netlist["instances"].keys()),
|
||
"connections": netlist["connections"],
|
||
"ports": netlist["ports"],
|
||
"event_count": len(events),
|
||
},
|
||
f,
|
||
indent=2,
|
||
)
|
||
print(f"Wrote netlist JSON: {out_json.resolve()}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import cmath
|
||
main()
|