# ==============================================================================
# file_id: SOM-SCR-PENDING-v1.0.0
# name: wavestack.py
# description: Corrected WaveStack: complex amplitudes, sqrt(2) splitter, PHASE/VALVE/MIX/DET, energy accounting; stdlib only (stamp pending: Witness assigns)
# project_id: SOMACO-FLUID
# category: scr
# tags: [fluid, rpn, python, reference]
# created: 2026-10-25
# modified: 2026-10-25
# version: 1.0.0
# agent_id: devin
# execution: python wavestack.py
# ==============================================================================
# Based on @Voltardark's WaveStack (x.com/Voltardark/status/2114476681951941026).
# Changes: waves are complex (amplitude + phase), SPLIT divides by sqrt(2) so energy is
# conserved, INTF is a Y-combiner (A+B)/sqrt(2), PHASE/VALVE/MIX/DET added, energy ledger.
import cmath
import math

ALPHA = 0.7


class WaveStack:
    def __init__(self):
        self.stack = []
        self.injected = 0.0
        self.radiated = 0.0

    def push(self, val):
        self.stack.append(complex(val))

    def pop(self):
        if not self.stack:
            raise IndexError("Stack Underflow: Missing wave states for the operator.")
        return self.stack.pop()

    @staticmethod
    def energy(*waves):
        return sum(abs(w) ** 2 for w in waves)

    def _log(self, msg):
        print([f"{abs(w):.3f}∠{(math.degrees(cmath.phase(w)) if abs(w) > 1e-12 else 0):.0f}°" for w in self.stack], "<--", msg)

    def execute_rpn(self, expression):
        for token in expression:
            if isinstance(token, (int, float, complex)):
                self.push(token)
                self.injected += self.energy(token)
                self._log(f"Push wave {token}")
                continue
            if token == "VALVE":
                pass  # a bare number already injects a wave; VALVE is a no-op alias
            elif token == "SPLIT":
                w = self.pop()
                self.push(w / math.sqrt(2))
                self.push(w / math.sqrt(2))
            elif token == "INTF":
                b, a = self.pop(), self.pop()
                out = (a + b) / math.sqrt(2)
                self.radiated += self.energy(a, b) - self.energy(out)
                self.push(out)
            elif token == "MIX":
                b, a = self.pop(), self.pop()
                self.push((a + b) / math.sqrt(2))
                self.push((a - b) / math.sqrt(2))
            elif token == "PHASE":
                theta, w = self.pop(), self.pop()
                self.injected -= self.energy(theta)  # theta is a parameter, not a wave
                self.push(w * cmath.exp(1j * math.radians(theta.real)))
            elif token == "ATTN":
                w = self.pop()
                self.radiated += self.energy(w) * (1 - ALPHA ** 2)
                self.push(w * ALPHA)
            elif token == "SWAP":
                b, a = self.pop(), self.pop()
                self.push(b)
                self.push(a)
            elif token == "DET":
                w = self.pop()
                self.push(abs(w) ** 2)
                continue  # a detector converts the wave to a reading; skip the energy check
            else:
                raise ValueError(f"unknown token {token!r}")
            self._log(f"Execute {token}")
        return self.stack


if __name__ == "__main__":
    for name, prog in [
        ("thread program", [1.2, 0.8, "INTF", "SPLIT", "ATTN"]),
        ("Mach-Zehnder bright", [1, "SPLIT", "INTF"]),
        ("Mach-Zehnder dark", [1, "SPLIT", 180, "PHASE", "INTF"]),
    ]:
        print(f"\n== {name}: {prog}")
        e = WaveStack()
        out = e.execute_rpn(prog)
        print(f"final: {[round(abs(w), 4) for w in out]}  energy in={e.injected:.4f} "
              f"out={e.energy(*out):.4f} radiated={e.radiated:.4f}")
