"""
Sound Block Virtual FPGA — Tests for the 5 FPGA primitives.

Every physical FPGA component has a Sound Block equivalent.
These tests verify:
  16. LUT (Spectral Truth Table) — arbitrary N-input boolean function
  17. Persistent Routing (Standing Wave) — configure-once signal routing
  18. Dual-Port Memory (Harmonic Dual-Port) — two independent port access
  19. Clock Domains (Phase Mask) — multi-clock virtual FPGA
  20. Stateful Circuit (Persistent Basin) — hot-swap with state preservation

Run:
    cd afs-engine
    python3 tests/test_sb_virtual_fpga.py -v
"""

import unittest
import numpy as np
import time
import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from afs.gpu.sb_fabric import SoundBlockFabric, FabricCircuit, CompiledSchedule, gpu_available


# ═══════════════════════════════════════════════════════════════════════════
# Primitive 16: LUT (Spectral Truth Table)
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestLUT(unittest.TestCase):
    """Binary LUT — discrete resonance cavity."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_2input_and_gate(self):
        """2-input AND gate: output = A & B."""
        # Truth table: [0,0,0,1] — indices: 00=0, 01=0, 10=0, 11=1
        table = np.array([0, 0, 0, 1], dtype=np.float32)
        self.fabric.configure_lut('and2', n_inputs=2, truth_table=table)

        inputs = np.array([
            [0, 0],
            [0, 1],
            [1, 0],
            [1, 1],
        ], dtype=np.float32)

        result = self.fabric.lut_eval('and2', inputs)
        np.testing.assert_array_equal(result, [0, 0, 0, 1])

    def test_2input_or_gate(self):
        """2-input OR gate: output = A | B."""
        table = np.array([0, 1, 1, 1], dtype=np.float32)
        self.fabric.configure_lut('or2', n_inputs=2, truth_table=table)

        inputs = np.array([
            [0, 0], [0, 1], [1, 0], [1, 1],
        ], dtype=np.float32)
        result = self.fabric.lut_eval('or2', inputs)
        np.testing.assert_array_equal(result, [0, 1, 1, 1])

    def test_2input_xor_gate(self):
        """2-input XOR gate."""
        table = np.array([0, 1, 1, 0], dtype=np.float32)
        self.fabric.configure_lut('xor2', n_inputs=2, truth_table=table)

        inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=np.float32)
        result = self.fabric.lut_eval('xor2', inputs)
        np.testing.assert_array_equal(result, [0, 1, 1, 0])

    def test_3input_majority(self):
        """3-input majority function: output = 1 if 2+ inputs are 1."""
        # 8-entry table for 3 inputs
        table = np.array([0, 0, 0, 1, 0, 1, 1, 1], dtype=np.float32)
        self.fabric.configure_lut('maj3', n_inputs=3, truth_table=table)

        inputs = np.array([
            [0, 0, 0],  # 0
            [1, 0, 0],  # 0
            [0, 1, 0],  # 0
            [1, 1, 0],  # 1
            [0, 0, 1],  # 0
            [1, 0, 1],  # 1
            [0, 1, 1],  # 1
            [1, 1, 1],  # 1
        ], dtype=np.float32)
        result = self.fabric.lut_eval('maj3', inputs)
        np.testing.assert_array_equal(result, [0, 0, 0, 1, 0, 1, 1, 1])

    def test_4input_lut(self):
        """4-input LUT: arbitrary 16-entry truth table."""
        # 4-LUT: 16 entries
        np.random.seed(42)
        table = np.random.randint(0, 2, size=16).astype(np.float32)
        self.fabric.configure_lut('lut4', n_inputs=4, truth_table=table)

        # Exhaustive test: all 16 input combinations
        inputs = np.array([[
            (i >> 0) & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1
        ] for i in range(16)], dtype=np.float32)

        result = self.fabric.lut_eval('lut4', inputs)
        np.testing.assert_array_equal(result, table)

    def test_6input_lut(self):
        """6-input LUT: matches FPGA 6-LUT (64 entries)."""
        np.random.seed(123)
        table = np.random.randint(0, 2, size=64).astype(np.float32)
        self.fabric.configure_lut('lut6', n_inputs=6, truth_table=table)

        # Test all 64 combinations
        inputs = np.array([[
            (i >> b) & 1 for b in range(6)
        ] for i in range(64)], dtype=np.float32)

        result = self.fabric.lut_eval('lut6', inputs)
        np.testing.assert_array_equal(result, table)
        print(f"\n  6-LUT: 64 entries, all correct")

    def test_lut_wrong_size_raises(self):
        """Mismatched table size should raise."""
        with self.assertRaises(ValueError):
            self.fabric.configure_lut('bad', n_inputs=3,
                                      truth_table=np.zeros(5, dtype=np.float32))

    def test_lut_float_output(self):
        """LUT can store float values, not just binary."""
        table = np.array([0.0, 0.25, 0.5, 1.0], dtype=np.float32)
        self.fabric.configure_lut('float2', n_inputs=2, truth_table=table)

        inputs = np.array([[0, 0], [1, 0], [0, 1], [1, 1]], dtype=np.float32)
        result = self.fabric.lut_eval('float2', inputs)
        np.testing.assert_array_almost_equal(result, [0.0, 0.25, 0.5, 1.0])


# ═══════════════════════════════════════════════════════════════════════════
# Primitive 17: Persistent Routing (Standing Wave Routing)
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestPersistentRouting(unittest.TestCase):
    """Switch matrix — standing wave routing pattern."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_simple_routing(self):
        """Route element 0 → element 2, element 1 → element 3."""
        self.fabric.configure_routing(
            src_indices=np.array([0, 1]),
            dst_indices=np.array([2, 3]),
        )
        data = np.array([10.0, 20.0, 0.0, 0.0], dtype=np.float32)
        self.fabric.upload('a', data)
        self.fabric.route('a', output_size=4)
        result = self.fabric.download('a')
        self.assertAlmostEqual(result[2], 10.0, places=4)
        self.assertAlmostEqual(result[3], 20.0, places=4)

    def test_fanout_routing(self):
        """One source fans out to multiple destinations."""
        self.fabric.configure_routing(
            src_indices=np.array([0, 0, 0]),
            dst_indices=np.array([1, 2, 3]),
        )
        data = np.array([5.0, 0.0, 0.0, 0.0], dtype=np.float32)
        self.fabric.upload('a', data)
        self.fabric.route('a', output_size=4)
        result = self.fabric.download('a')
        for i in [1, 2, 3]:
            self.assertAlmostEqual(result[i], 5.0, places=4)

    def test_routing_with_strengths(self):
        """Routing with coupling strengths (attenuated signals)."""
        self.fabric.configure_routing(
            src_indices=np.array([0, 1]),
            dst_indices=np.array([2, 2]),
            strengths=np.array([0.5, 0.3]),
        )
        data = np.array([10.0, 10.0, 0.0], dtype=np.float32)
        self.fabric.upload('a', data)
        self.fabric.route('a', output_size=3)
        result = self.fabric.download('a')
        # dst[2] = src[0]*0.5 + src[1]*0.3 = 5.0 + 3.0 = 8.0
        self.assertAlmostEqual(result[2], 8.0, places=4)

    def test_routing_persistence(self):
        """Same routing config reused across multiple route() calls."""
        self.fabric.configure_routing(
            src_indices=np.array([0]),
            dst_indices=np.array([1]),
        )

        for val in [1.0, 2.0, 3.0]:
            data = np.array([val, 0.0], dtype=np.float32)
            self.fabric.upload('a', data)
            self.fabric.route('a', output_size=2)
            result = self.fabric.download('a')
            self.assertAlmostEqual(result[1], val, places=4)

    def test_route_through(self):
        """Non-destructive routing between slots."""
        self.fabric.configure_routing(
            src_indices=np.array([0, 1]),
            dst_indices=np.array([0, 1]),
        )
        data = np.array([7.0, 3.0], dtype=np.float32)
        self.fabric.upload('a', data)
        self.fabric.route_through('a', 'out', output_size=2)

        # Source unchanged
        src = self.fabric.download('a')
        self.assertAlmostEqual(src[0], 7.0, places=4)

        # Destination has routed values
        dst = self.fabric.download('out')
        self.assertAlmostEqual(dst[0], 7.0, places=4)


# ═══════════════════════════════════════════════════════════════════════════
# Primitive 18: Dual-Port Memory (Harmonic Dual-Port)
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestDualPortMemory(unittest.TestCase):
    """True dual-port BRAM — two resonance probes on shared spectrum."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_basic_write_read(self):
        """Write via port 0, read via port 1."""
        self.fabric.create_dual_port('mem0', size=16)
        self.fabric.dual_port_write('mem0', port=0,
                                    addresses=np.array([3, 7]),
                                    values=np.array([3.14, 2.72]))
        result = self.fabric.dual_port_read('mem0', port=1,
                                            addresses=np.array([3, 7, 0]))
        self.assertAlmostEqual(result[0], 3.14, places=4)
        self.assertAlmostEqual(result[1], 2.72, places=4)
        self.assertAlmostEqual(result[2], 0.0, places=4)

    def test_dual_port_independence(self):
        """Both ports can read different addresses simultaneously."""
        self.fabric.create_dual_port('mem1', size=8)
        self.fabric.dual_port_write('mem1', port=0,
                                    addresses=np.arange(8),
                                    values=np.arange(8, dtype=np.float32) * 10)

        read_a = self.fabric.dual_port_read('mem1', port=0,
                                            addresses=np.array([0, 2, 4]))
        read_b = self.fabric.dual_port_read('mem1', port=1,
                                            addresses=np.array([1, 3, 5]))

        np.testing.assert_array_almost_equal(read_a, [0, 20, 40])
        np.testing.assert_array_almost_equal(read_b, [10, 30, 50])

    def test_simultaneous_read_write(self):
        """True dual-port: read + write in same cycle (read-before-write)."""
        self.fabric.create_dual_port('mem2', size=4)
        self.fabric.dual_port_write('mem2', port=0,
                                    addresses=np.array([0, 1, 2, 3]),
                                    values=np.array([10, 20, 30, 40], dtype=np.float32))

        # Read from addr 0,1 while writing to addr 2,3
        result = self.fabric.dual_port_read_write(
            'mem2',
            read_addrs=np.array([0, 1]),
            write_addrs=np.array([2, 3]),
            write_vals=np.array([99, 88], dtype=np.float32),
        )

        # Read sees pre-write values
        np.testing.assert_array_almost_equal(result, [10, 20])

        # Write took effect
        post = self.fabric.dual_port_read('mem2', port=0,
                                          addresses=np.array([2, 3]))
        np.testing.assert_array_almost_equal(post, [99, 88])

    def test_write_write_priority(self):
        """Port 1 has priority over port 0 on same address."""
        self.fabric.create_dual_port('mem3', size=4)

        # Port 0 writes 10, Port 1 writes 99 (Port 1 wins)
        self.fabric.dual_port_write('mem3', port=0,
                                    addresses=np.array([0]),
                                    values=np.array([10.0]))
        self.fabric.dual_port_write('mem3', port=1,
                                    addresses=np.array([0]),
                                    values=np.array([99.0]))

        result = self.fabric.dual_port_read('mem3', port=0,
                                            addresses=np.array([0]))
        self.assertAlmostEqual(result[0], 99.0, places=4)


# ═══════════════════════════════════════════════════════════════════════════
# Primitive 19: Clock Domains (Phase Mask)
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestClockDomains(unittest.TestCase):
    """Multi-clock virtual FPGA — phase mask carrier frequencies."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_single_domain_every_tick(self):
        """Domain with divisor=1: active every tick."""
        self.fabric.configure_clock_domain('fast', element_range=(0, 4), divisor=1)

        for _ in range(4):
            self.assertTrue(self.fabric.domain_active('fast'))
            self.fabric.tick()

    def test_half_speed_domain(self):
        """Domain with divisor=2: active every other tick."""
        self.fabric._global_tick = 0
        self.fabric.configure_clock_domain('slow', element_range=(0, 4), divisor=2)

        active_ticks = []
        for i in range(8):
            active_ticks.append(self.fabric.domain_active('slow'))
            self.fabric.tick()

        # Should be active on ticks 0, 2, 4, 6 (even)
        self.assertEqual(active_ticks, [True, False, True, False,
                                        True, False, True, False])

    def test_quarter_speed_domain(self):
        """Domain with divisor=4: active every 4th tick."""
        self.fabric._global_tick = 0
        self.fabric.configure_clock_domain('quarter', element_range=(0, 4), divisor=4)

        active_ticks = []
        for _ in range(8):
            active_ticks.append(self.fabric.domain_active('quarter'))
            self.fabric.tick()

        self.assertEqual(active_ticks, [True, False, False, False,
                                        True, False, False, False])

    def test_phase_offset(self):
        """Domain with phase offset: active at different point in cycle."""
        self.fabric._global_tick = 0
        self.fabric.configure_clock_domain('phase1', element_range=(0, 4),
                                           divisor=2, phase=1)

        active_ticks = []
        for _ in range(8):
            active_ticks.append(self.fabric.domain_active('phase1'))
            self.fabric.tick()

        # Active on ticks 1, 3, 5, 7 (odd)
        self.assertEqual(active_ticks, [False, True, False, True,
                                        False, True, False, True])

    def test_multiple_domains(self):
        """Two domains at different speeds running simultaneously."""
        self.fabric._global_tick = 0
        self.fabric.configure_clock_domain('fast', element_range=(0, 4), divisor=1)
        self.fabric.configure_clock_domain('slow', element_range=(4, 8), divisor=3)

        fast_count = 0
        slow_count = 0
        for _ in range(12):
            if self.fabric.domain_active('fast'):
                fast_count += 1
            if self.fabric.domain_active('slow'):
                slow_count += 1
            self.fabric.tick()

        self.assertEqual(fast_count, 12)  # every tick
        self.assertEqual(slow_count, 4)   # every 3rd tick

    def test_clock_mask_selective_update(self):
        """Clock mask selectively updates elements."""
        self.fabric._global_tick = 0
        self.fabric.configure_clock_domain('fast', element_range=(0, 2), divisor=1)
        self.fabric.configure_clock_domain('slow', element_range=(2, 4), divisor=2)

        data = np.array([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
        self.fabric.upload('a', data)

        # Tick 0: both domains active
        mask = self.fabric.build_clock_mask()
        np.testing.assert_array_equal(mask, [True, True, True, True])

        self.fabric.tick('a', update_fn=lambda d: d + 1.0)

        # Tick 1: fast active, slow not
        mask = self.fabric.build_clock_mask()
        np.testing.assert_array_equal(mask, [True, True, False, False])

        self.fabric.tick('a', update_fn=lambda d: d + 1.0)

        result = self.fabric.download('a')
        # Elements 0,1: updated twice (tick 0 + tick 1)
        self.assertAlmostEqual(result[0], 3.0, places=4)
        self.assertAlmostEqual(result[1], 3.0, places=4)
        # Elements 2,3: updated once (tick 0 only)
        self.assertAlmostEqual(result[2], 2.0, places=4)
        self.assertAlmostEqual(result[3], 2.0, places=4)
        print(f"\n  Clock domains: fast=2 updates, slow=1 update ✓")


# ═══════════════════════════════════════════════════════════════════════════
# Primitive 20: Stateful Circuit (Persistent Basin State)
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestStatefulCircuit(unittest.TestCase):
    """Stateful circuit with hot-swap — persistent basin state."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def _adder_fn(self, inputs, state):
        """Simple accumulator: output = input + state, state += input."""
        outputs = inputs + state
        new_state = state + inputs
        return outputs, new_state

    def _doubler_fn(self, inputs, state):
        """Doubles input: output = input * 2."""
        return inputs * 2, state

    def _counter_fn(self, inputs, state):
        """Counter: state increments by 1 each tick."""
        new_state = state + 1.0
        return new_state, new_state

    def test_basic_circuit(self):
        """Two-block circuit: A → B."""
        def block_a(inp, state):
            return np.array([5.0]), state

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'A', 'fn': block_a, 'state_size': 1},
                {'name': 'B', 'fn': self._doubler_fn, 'state_size': 1},
            ],
            connections=[
                {'from_block': 'A', 'from_port': 0, 'to_block': 'B', 'to_port': 0},
            ],
        )

        # Tick 1: A outputs 5, B hasn't received it yet (0 input)
        r1 = circuit.tick()
        self.assertAlmostEqual(r1['A'][0], 5.0)
        self.assertAlmostEqual(r1['B'][0], 0.0)

        # Tick 2: B receives 5 from A, outputs 10
        r2 = circuit.tick()
        self.assertAlmostEqual(r2['B'][0], 10.0)

    def test_accumulator_state(self):
        """State persists across ticks."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'acc', 'fn': self._adder_fn, 'state_size': 1}],
            connections=[],
        )

        circuit.set_input('acc', np.array([1.0]))
        circuit.tick()
        state = circuit.get_state('acc')
        self.assertAlmostEqual(state[0], 1.0)

        circuit.set_input('acc', np.array([1.0]))
        circuit.tick()
        state = circuit.get_state('acc')
        self.assertAlmostEqual(state[0], 2.0)

        circuit.set_input('acc', np.array([1.0]))
        circuit.tick()
        state = circuit.get_state('acc')
        self.assertAlmostEqual(state[0], 3.0)

    def test_hot_swap_preserves_other_state(self):
        """Hot-swap one block, other blocks keep state."""
        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'counter', 'fn': self._counter_fn, 'state_size': 1},
                {'name': 'swappable', 'fn': self._doubler_fn, 'state_size': 1},
            ],
            connections=[],
        )

        # Run 5 ticks — counter accumulates
        for _ in range(5):
            circuit.tick()
        counter_state = circuit.get_state('counter')
        self.assertAlmostEqual(counter_state[0], 5.0)

        # Hot-swap 'swappable' — counter should keep its state
        def new_fn(inp, state):
            return inp * 3, state
        circuit.hot_swap('swappable', new_fn)

        counter_state_after = circuit.get_state('counter')
        self.assertAlmostEqual(counter_state_after[0], 5.0,
                               msg="Counter state lost after hot-swap!")
        print(f"\n  Hot-swap: counter state preserved at {counter_state_after[0]} ✓")

    def test_hot_swap_resets_swapped_block(self):
        """Swapped block's state resets to zero."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'acc', 'fn': self._adder_fn, 'state_size': 1}],
            connections=[],
        )

        # Accumulate to 10
        for _ in range(10):
            circuit.set_input('acc', np.array([1.0]))
            circuit.tick()
        self.assertAlmostEqual(circuit.get_state('acc')[0], 10.0)

        # Hot-swap resets state
        circuit.hot_swap('acc', self._counter_fn)
        self.assertAlmostEqual(circuit.get_state('acc')[0], 0.0)

    def test_circuit_with_routing(self):
        """3-block pipeline: A → B → C."""
        def const_5(inp, state):
            return np.array([5.0]), state

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'A', 'fn': const_5, 'state_size': 1},
                {'name': 'B', 'fn': self._doubler_fn, 'state_size': 1},
                {'name': 'C', 'fn': self._adder_fn, 'state_size': 1},
            ],
            connections=[
                {'from_block': 'A', 'from_port': 0, 'to_block': 'B', 'to_port': 0},
                {'from_block': 'B', 'from_port': 0, 'to_block': 'C', 'to_port': 0},
            ],
        )

        # Tick 1: A=5
        circuit.tick()
        # Tick 2: B receives 5, outputs 10
        circuit.tick()
        # Tick 3: C receives 10, outputs 10 (state=10)
        circuit.tick()

        state_c = circuit.get_state('C')
        self.assertAlmostEqual(state_c[0], 10.0)

    def test_connection_strength(self):
        """Connections with coupling strength (attenuated signals)."""
        def const_10(inp, state):
            return np.array([10.0]), state

        def identity(inp, state):
            return inp.copy(), state

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'src', 'fn': const_10, 'state_size': 1},
                {'name': 'dst', 'fn': identity, 'state_size': 1},
            ],
            connections=[
                {'from_block': 'src', 'from_port': 0,
                 'to_block': 'dst', 'to_port': 0, 'strength': 0.5},
            ],
        )

        circuit.tick()  # src outputs 10
        r = circuit.tick()  # dst receives 10*0.5 = 5
        self.assertAlmostEqual(r['dst'][0], 5.0)


# ═══════════════════════════════════════════════════════════════════════════
# Integration & Benchmark
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestVirtualFPGAIntegration(unittest.TestCase):
    """Integration tests combining multiple FPGA primitives."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_lut_chain_half_adder(self):
        """Half adder: Sum = A XOR B, Carry = A AND B."""
        xor_table = np.array([0, 1, 1, 0], dtype=np.float32)
        and_table = np.array([0, 0, 0, 1], dtype=np.float32)
        self.fabric.configure_lut('xor', n_inputs=2, truth_table=xor_table)
        self.fabric.configure_lut('and', n_inputs=2, truth_table=and_table)

        test_cases = [
            ([0, 0], 0, 0),
            ([0, 1], 1, 0),
            ([1, 0], 1, 0),
            ([1, 1], 0, 1),
        ]

        for inputs, exp_sum, exp_carry in test_cases:
            inp = np.array([inputs], dtype=np.float32)
            s = self.fabric.lut_eval('xor', inp)
            c = self.fabric.lut_eval('and', inp)
            self.assertEqual(s[0], exp_sum, f"Sum wrong for {inputs}")
            self.assertEqual(c[0], exp_carry, f"Carry wrong for {inputs}")

        print(f"\n  Half adder: all 4 cases correct ✓")

    def test_routed_circuit_with_dual_port(self):
        """Circuit that writes to dual-port memory."""
        self.fabric.create_dual_port('scratch', size=8)

        # Write pattern then read back
        addrs = np.arange(8)
        vals = np.arange(8, dtype=np.float32) * 3.14

        self.fabric.dual_port_write('scratch', port=0,
                                    addresses=addrs, values=vals)
        result = self.fabric.dual_port_read('scratch', port=1,
                                            addresses=addrs)
        np.testing.assert_array_almost_equal(result, vals, decimal=2)

    def test_20_primitives_listed(self):
        """Fabric should list all 20 primitives."""
        prims = self.fabric.primitives
        self.assertEqual(len(prims), 20)
        # Check the 5 new ones are present
        names = ' '.join(prims)
        self.assertIn('lut', names)
        self.assertIn('routing', names)
        self.assertIn('dual_port', names)
        self.assertIn('clock_domain', names)
        self.assertIn('circuit', names)
        print(f"\n  20 primitives: 15 compute + 5 Virtual FPGA ✓")


@unittest.skipUnless(gpu_available(), "No GPU available")
class TestVirtualFPGABenchmark(unittest.TestCase):
    """Performance benchmarks for Virtual FPGA primitives."""

    def test_lut_throughput(self):
        """LUT evaluation throughput."""
        fabric = SoundBlockFabric(max_size=10240)
        np.random.seed(42)
        table = np.random.randint(0, 2, size=64).astype(np.float32)
        fabric.configure_lut('bench', n_inputs=6, truth_table=table)

        # 10K evaluations
        inputs = np.random.randint(0, 2, size=(10000, 6)).astype(np.float32)

        # Warmup
        fabric.lut_eval('bench', inputs)

        times = []
        for _ in range(10):
            t0 = time.perf_counter()
            fabric.lut_eval('bench', inputs)
            times.append((time.perf_counter() - t0) * 1000)

        avg = np.mean(times)
        print(f"\n  LUT Benchmark (6-input, 10K evals):")
        print(f"    Mean: {avg:.2f}ms ± {np.std(times):.2f}ms")
        print(f"    Throughput: {10000 / avg * 1000:.0f} evals/sec")

    def test_circuit_tick_speed(self):
        """Stateful circuit tick throughput."""
        fabric = SoundBlockFabric(max_size=10240)

        def simple_fn(inp, state):
            return inp + state, state + 0.01

        blocks = [{'name': f'b{i}', 'fn': simple_fn, 'state_size': 4}
                  for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        circuit = fabric.create_circuit(blocks, connections)

        # Warmup
        for _ in range(10):
            circuit.tick()

        times = []
        for _ in range(100):
            t0 = time.perf_counter()
            circuit.tick()
            times.append((time.perf_counter() - t0) * 1000)

        avg = np.mean(times)
        print(f"\n  Circuit Benchmark (10 blocks, 4-wide state):")
        print(f"    Mean: {avg:.3f}ms/tick ± {np.std(times):.3f}ms")
        print(f"    Throughput: {1.0 / avg * 1000:.0f} ticks/sec")

    def test_routing_throughput(self):
        """Persistent routing throughput."""
        fabric = SoundBlockFabric(max_size=10240)

        n = 1000
        src = np.random.randint(0, n, size=n * 2).astype(np.int32)
        dst = np.random.randint(0, n, size=n * 2).astype(np.int32)
        fabric.configure_routing(src, dst)

        data = np.random.randn(n).astype(np.float32)
        fabric.upload('a', data)

        # Warmup
        fabric.route('a', output_size=n)

        times = []
        for _ in range(20):
            fabric.upload('a', data)
            t0 = time.perf_counter()
            fabric.route('a', output_size=n)
            times.append((time.perf_counter() - t0) * 1000)

        avg = np.mean(times)
        print(f"\n  Routing Benchmark (1K elements, 2K routes):")
        print(f"    Mean: {avg:.2f}ms ± {np.std(times):.2f}ms")


# ═══════════════════════════════════════════════════════════════════════════
# ROADBLOCK → SOUND BLOCK: Three Conversions
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestRoadblock1_BatchDepth(unittest.TestCase):
    """Roadblock 1: Latency → Sound Block: Batch Depth (Fused Tick)."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def _counter_fn(self, inputs, state):
        new_state = state + 1.0
        return new_state, new_state

    def _adder_fn(self, inputs, state):
        outputs = inputs + state
        new_state = state + inputs
        return outputs, new_state

    def test_fused_matches_sequential(self):
        """run_fused(N) must produce identical results to N sequential tick() calls."""
        def const_1(inp, state):
            return np.array([1.0]), state

        # Sequential
        circuit_seq = self.fabric.create_circuit(
            blocks=[
                {'name': 'src', 'fn': const_1, 'state_size': 1},
                {'name': 'acc', 'fn': self._adder_fn, 'state_size': 1},
            ],
            connections=[
                {'from_block': 'src', 'from_port': 0, 'to_block': 'acc', 'to_port': 0},
            ],
        )
        for _ in range(100):
            circuit_seq.tick()
        seq_state = circuit_seq.get_state('acc')

        # Fused
        circuit_fused = self.fabric.create_circuit(
            blocks=[
                {'name': 'src', 'fn': const_1, 'state_size': 1},
                {'name': 'acc', 'fn': self._adder_fn, 'state_size': 1},
            ],
            connections=[
                {'from_block': 'src', 'from_port': 0, 'to_block': 'acc', 'to_port': 0},
            ],
        )
        result = circuit_fused.run_fused(100)

        fused_state = result['states']['acc']
        np.testing.assert_array_almost_equal(seq_state, fused_state, decimal=4,
            err_msg="Fused and sequential tick must produce identical state")
        print(f"\n  Fused vs Sequential: match ✓ (state={fused_state[0]:.1f})")

    def test_fused_counter_1000_ticks(self):
        """Counter runs 1000 ticks fused — state should be 1000."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        result = circuit.run_fused(1000)
        self.assertAlmostEqual(result['states']['ctr'][0], 1000.0, places=1)
        print(f"\n  1000 fused ticks: {result['per_tick_ns']:.0f}ns/tick, "
              f"total {result['total_ms']:.2f}ms")

    def test_fused_capture_outputs(self):
        """Captured outputs at specific ticks."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        result = circuit.run_fused(100, capture_ticks=[0, 49, 99])
        self.assertIn(0, result['captured'])
        self.assertIn(49, result['captured'])
        self.assertIn(99, result['captured'])
        # Tick 49 → counter at 50 (1-indexed from tick 0)
        self.assertAlmostEqual(result['captured'][49]['ctr'][0], 50.0, places=1)

    def test_fused_latency_benchmark(self):
        """Benchmark: per-tick latency at different batch sizes."""
        def simple_fn(inp, state):
            return inp + state, state + 0.001

        blocks = [{'name': f'b{i}', 'fn': simple_fn, 'state_size': 1}
                  for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        print(f"\n  Batch Depth Benchmark (10 blocks):")
        for n_ticks in [1, 10, 100, 1000]:
            circuit = self.fabric.create_circuit(blocks, connections)
            # Warmup
            circuit.run_fused(10)

            circuit2 = self.fabric.create_circuit(blocks, connections)
            result = circuit2.run_fused(n_ticks)
            print(f"    N={n_ticks:>5}: {result['per_tick_ns']:.0f}ns/tick "
                  f"({result['total_ms']:.2f}ms total)")


@unittest.skipUnless(gpu_available(), "No GPU available")
class TestRoadblock2_DispatchGating(unittest.TestCase):
    """Roadblock 2: Power → Sound Block: Dispatch Gating (Clock Gating)."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def _counter_fn(self, inputs, state):
        new_state = state + 1.0
        return new_state, new_state

    def test_clock_gating_half_speed(self):
        """Block at divisor=2 should count half as fast."""
        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'fast', 'fn': self._counter_fn, 'state_size': 1},
                {'name': 'slow', 'fn': self._counter_fn, 'state_size': 1},
            ],
            connections=[],
        )
        result = circuit.run_fused(100, clock_domains={
            'fast': {'divisor': 1},
            'slow': {'divisor': 2},
        })

        fast_count = result['states']['fast'][0]
        slow_count = result['states']['slow'][0]
        self.assertAlmostEqual(fast_count, 100.0, places=1)
        self.assertAlmostEqual(slow_count, 50.0, places=1)
        print(f"\n  Clock gating: fast={fast_count:.0f}, slow={slow_count:.0f} ✓")

    def test_clock_gating_quarter_speed(self):
        """Block at divisor=4 should count quarter as fast."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'q', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        result = circuit.run_fused(100, clock_domains={
            'q': {'divisor': 4},
        })
        self.assertAlmostEqual(result['states']['q'][0], 25.0, places=1)

    def test_gating_power_proxy(self):
        """Gated circuit should run faster (fewer block evaluations)."""
        def heavy_fn(inp, state):
            # Simulate heavy computation
            x = state + 0.001
            for _ in range(10):
                x = np.tanh(x) + 0.001
            return x, x

        blocks = [{'name': f'b{i}', 'fn': heavy_fn, 'state_size': 4}
                  for i in range(20)]

        # 100% active
        c1 = self.fabric.create_circuit(blocks, [])
        r1 = c1.run_fused(100)

        # 10% active (divisor=10)
        c2 = self.fabric.create_circuit(blocks, [])
        r2 = c2.run_fused(100, clock_domains={
            f'b{i}': {'divisor': 10} for i in range(20)
        })

        ratio = r1['total_ms'] / max(r2['total_ms'], 0.001)
        print(f"\n  Power proxy: 100% active={r1['total_ms']:.2f}ms, "
              f"10% active={r2['total_ms']:.2f}ms, ratio={ratio:.1f}x")
        # Gated should be meaningfully faster
        self.assertGreater(ratio, 2.0, "Clock gating should provide >2x speedup")

    def test_phase_offset_gating(self):
        """Two blocks in same divisor but different phase — alternate ticks."""
        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'even', 'fn': self._counter_fn, 'state_size': 1},
                {'name': 'odd', 'fn': self._counter_fn, 'state_size': 1},
            ],
            connections=[],
        )
        result = circuit.run_fused(100, clock_domains={
            'even': {'divisor': 2, 'phase': 0},
            'odd': {'divisor': 2, 'phase': 1},
        })
        even = result['states']['even'][0]
        odd = result['states']['odd'][0]
        self.assertAlmostEqual(even, 50.0, places=1)
        self.assertAlmostEqual(odd, 50.0, places=1)
        print(f"\n  Phase offset: even={even:.0f}, odd={odd:.0f} (alternating) ✓")


@unittest.skipUnless(gpu_available(), "No GPU available")
class TestRoadblock3_Determinism(unittest.TestCase):
    """Roadblock 3: Determinism → Sound Block: Pre-Computed Schedule."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def _counter_fn(self, inputs, state):
        new_state = state + 1.0
        return new_state, new_state

    def _adder_fn(self, inputs, state):
        outputs = inputs + state
        new_state = state + inputs
        return outputs, new_state

    def test_compile_creates_schedule(self):
        """compile() produces a valid CompiledSchedule."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'a', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        schedule = circuit.compile(100)
        self.assertEqual(schedule.n_ticks, 100)
        self.assertEqual(schedule.n_blocks, 1)
        self.assertGreater(len(schedule.capture_ticks), 0)
        self.assertEqual(schedule.total_active_block_ticks, 100)

    def test_deterministic_replay(self):
        """Same schedule → identical output across 10 runs."""
        def const_1(inp, state):
            return np.array([1.0]), state

        blocks = [
            {'name': 'src', 'fn': const_1, 'state_size': 1},
            {'name': 'acc', 'fn': self._adder_fn, 'state_size': 1},
        ]
        connections = [
            {'from_block': 'src', 'from_port': 0, 'to_block': 'acc', 'to_port': 0},
        ]

        results = []
        for _ in range(10):
            circuit = self.fabric.create_circuit(blocks, connections)
            schedule = circuit.compile(200)
            result = circuit.execute_bounded(schedule)
            results.append(result['states']['acc'][0])

        # All 10 runs must be identical
        for i in range(1, 10):
            self.assertAlmostEqual(results[0], results[i], places=6,
                msg=f"Run 0 ({results[0]}) != Run {i} ({results[i]})")
        print(f"\n  Deterministic replay: 10 runs identical at {results[0]:.4f} ✓")
        self.assertTrue(all(r.get('deterministic', False)
                          for r in [self.fabric.create_circuit(blocks, connections)
                                    .execute_bounded(
                                        self.fabric.create_circuit(blocks, connections)
                                        .compile(10))]))

    def test_bounded_matches_fused(self):
        """execute_bounded() must match run_fused() output."""
        circuit_fused = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        r_fused = circuit_fused.run_fused(500)

        circuit_bounded = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        schedule = circuit_bounded.compile(500)
        r_bounded = circuit_bounded.execute_bounded(schedule)

        np.testing.assert_array_almost_equal(
            r_fused['states']['ctr'], r_bounded['states']['ctr'], decimal=4,
            err_msg="Bounded and fused must produce identical state")

    def test_gating_efficiency(self):
        """CompiledSchedule reports gating efficiency."""
        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'fast', 'fn': self._counter_fn, 'state_size': 1},
                {'name': 'slow', 'fn': self._counter_fn, 'state_size': 1},
            ],
            connections=[],
        )
        schedule = circuit.compile(100, clock_domains={
            'fast': {'divisor': 1},
            'slow': {'divisor': 4},
        })

        eff = schedule.gating_efficiency
        # 100 ticks × 2 blocks = 200 total. fast=100 active, slow=25 active = 125/200
        # gating = 1 - 125/200 = 0.375
        self.assertAlmostEqual(eff, 0.375, places=2)
        print(f"\n  Gating efficiency: {eff*100:.1f}% of block-ticks gated ✓")

    def test_schedule_capture_every(self):
        """capture_every captures at regular intervals."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1}],
            connections=[],
        )
        schedule = circuit.compile(100, capture_every=25)
        result = circuit.execute_bounded(schedule)

        # Should capture at 0, 25, 50, 75, 99
        self.assertIn(0, result['captured'])
        self.assertIn(25, result['captured'])
        self.assertIn(50, result['captured'])
        self.assertIn(75, result['captured'])
        self.assertIn(99, result['captured'])

    def test_bounded_benchmark(self):
        """Benchmark: bounded execution timing predictability."""
        def simple_fn(inp, state):
            return inp + state, state + 0.001

        blocks = [{'name': f'b{i}', 'fn': simple_fn, 'state_size': 1}
                  for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        times = []
        for _ in range(10):
            circuit = self.fabric.create_circuit(blocks, connections)
            schedule = circuit.compile(1000)
            result = circuit.execute_bounded(schedule)
            times.append(result['total_ms'])

        avg = np.mean(times)
        std = np.std(times)
        cv = std / avg * 100  # coefficient of variation
        print(f"\n  Bounded Execution Benchmark (10 blocks × 1000 ticks):")
        print(f"    Mean: {avg:.2f}ms ± {std:.2f}ms (CV={cv:.1f}%)")
        print(f"    Per-tick: {result['per_tick_ns']:.0f}ns")
        # Bounded execution should be predictable — low variance
        self.assertLess(cv, 30, "Coefficient of variation should be <30%")


# ═══════════════════════════════════════════════════════════════════════════
# GPU-DISPATCHED FUSED EXECUTION — Zero Python Overhead
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestGPUFusedExecution(unittest.TestCase):
    """GPU dispatch: N ticks in one shader invocation, zero Python per-tick."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=10240)

    def test_gpu_counter_1000(self):
        """GPU counter runs 1000 ticks — state should be 1000."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': counter_fn, 'state_size': 1,
                     'gpu_type': 'counter'}],
            connections=[],
        )
        result = circuit.run_fused_gpu(1000)
        self.assertTrue(result['gpu'])
        self.assertAlmostEqual(result['states']['ctr'][0], 1000.0, places=1)
        print(f"\n  GPU counter 1000 ticks: {result['per_tick_ns']:.1f}ns/tick "
              f"({result['total_ms']:.3f}ms total)")

    def test_gpu_counter_10000(self):
        """GPU counter 10K ticks — amortize dispatch overhead."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': counter_fn, 'state_size': 1,
                     'gpu_type': 'counter'}],
            connections=[],
        )
        result = circuit.run_fused_gpu(10000)
        self.assertAlmostEqual(result['states']['ctr'][0], 10000.0, places=1)
        print(f"\n  GPU counter 10K ticks: {result['per_tick_ns']:.1f}ns/tick "
              f"({result['total_ms']:.3f}ms total)")

    def test_gpu_matches_cpu_counter(self):
        """GPU and CPU fused must produce identical counter state."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        # CPU fused
        c_cpu = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': counter_fn, 'state_size': 1,
                     'gpu_type': 'counter'}],
            connections=[],
        )
        r_cpu = c_cpu.run_fused(500)

        # GPU fused
        c_gpu = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': counter_fn, 'state_size': 1,
                     'gpu_type': 'counter'}],
            connections=[],
        )
        r_gpu = c_gpu.run_fused_gpu(500)

        np.testing.assert_array_almost_equal(
            r_cpu['states']['ctr'], r_gpu['states']['ctr'], decimal=1,
            err_msg="GPU and CPU counter must match")
        print(f"\n  GPU vs CPU: both={r_gpu['states']['ctr'][0]:.0f} ✓"
              f" (GPU: {r_gpu['per_tick_ns']:.0f}ns, CPU: {r_cpu['per_tick_ns']:.0f}ns)")

    def test_gpu_accumulator_with_connection(self):
        """GPU accumulator receiving input via connection."""
        def const_fn(inp, state):
            return np.array([1.0]), state

        def acc_fn(inp, state):
            return inp + state, state + inp

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'src', 'fn': const_fn, 'state_size': 1,
                 'gpu_type': 'passthrough'},
                {'name': 'acc', 'fn': acc_fn, 'state_size': 1,
                 'gpu_type': 'accumulator'},
            ],
            connections=[
                {'from_block': 'src', 'from_port': 0,
                 'to_block': 'acc', 'to_port': 0, 'strength': 1.0},
            ],
        )

        # Run on GPU — src outputs passthrough (0 initially),
        # acc accumulates input each tick
        result = circuit.run_fused_gpu(100)
        # Passthrough with 0 input → 0 output, but connection routes it
        # This tests that routing works on GPU
        self.assertTrue(result['gpu'])
        print(f"\n  GPU accumulator with routing: "
              f"acc={result['states']['acc'][0]:.1f}")

    def test_gpu_clock_gating(self):
        """GPU clock gating: half-speed block counts half as much."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'fast', 'fn': counter_fn, 'state_size': 1,
                 'gpu_type': 'counter'},
                {'name': 'slow', 'fn': counter_fn, 'state_size': 1,
                 'gpu_type': 'counter'},
            ],
            connections=[],
        )
        result = circuit.run_fused_gpu(100, clock_domains={
            'fast': {'divisor': 1},
            'slow': {'divisor': 2},
        })
        fast = result['states']['fast'][0]
        slow = result['states']['slow'][0]
        self.assertAlmostEqual(fast, 100.0, places=1)
        self.assertAlmostEqual(slow, 50.0, places=1)
        print(f"\n  GPU clock gating: fast={fast:.0f}, slow={slow:.0f} ✓")

    def test_gpu_10block_pipeline_benchmark(self):
        """Benchmark: 10-block pipeline at various tick counts."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        blocks = [{'name': f'b{i}', 'fn': counter_fn, 'state_size': 1,
                   'gpu_type': 'counter'} for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        print(f"\n  GPU Pipeline Benchmark (10 blocks):")
        print(f"    {'N':>8}  {'Total':>10}  {'Per-tick':>12}  {'vs FPGA 3ns':>12}")
        for n_ticks in [10, 100, 1000, 10000]:
            circuit = self.fabric.create_circuit(blocks, connections)
            result = circuit.run_fused_gpu(n_ticks)
            ratio = result['per_tick_ns'] / 3.0
            print(f"    {n_ticks:>8}  {result['total_ms']:>9.3f}ms  "
                  f"{result['per_tick_ns']:>10.1f}ns  {ratio:>10.1f}x")

    def test_gpu_vs_cpu_speedup(self):
        """Compare GPU vs CPU fused execution speed."""
        def counter_fn(inp, state):
            return state + 1.0, state + 1.0

        blocks = [{'name': f'b{i}', 'fn': counter_fn, 'state_size': 1,
                   'gpu_type': 'counter'} for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        n = 1000

        # CPU
        c_cpu = self.fabric.create_circuit(blocks, connections)
        r_cpu = c_cpu.run_fused(n)

        # GPU
        c_gpu = self.fabric.create_circuit(blocks, connections)
        r_gpu = c_gpu.run_fused_gpu(n)

        speedup = r_cpu['per_tick_ns'] / max(r_gpu['per_tick_ns'], 1)
        print(f"\n  GPU vs CPU (10 blocks × {n} ticks):")
        print(f"    CPU: {r_cpu['per_tick_ns']:.0f}ns/tick")
        print(f"    GPU: {r_gpu['per_tick_ns']:.0f}ns/tick")
        print(f"    Speedup: {speedup:.1f}x")


# ═══════════════════════════════════════════════════════════════════════════
# SPECTRAL CIRCUIT — Virtual GPU Sound Block
# The circuit IS a waveform. One matmul = one complete tick.
# ═══════════════════════════════════════════════════════════════════════════

@unittest.skipUnless(gpu_available(), "No GPU available")
class TestSpectralCircuit(unittest.TestCase):
    """Spectral Circuit: frequency-domain circuit execution via matmul."""

    def setUp(self):
        self.fabric = SoundBlockFabric(max_size=1_048_576, max_matrix_dim=1024)

    def _counter_fn(self, inputs, state):
        new_state = state + 1.0
        return new_state, new_state

    def _acc_fn(self, inputs, state):
        return inputs + state, state + inputs

    def test_spectral_counter(self):
        """Spectral counter accumulates via bias + self-coupling."""
        circuit = self.fabric.create_circuit(
            blocks=[{'name': 'ctr', 'fn': self._counter_fn, 'state_size': 1,
                     'gpu_type': 'counter'}],
            connections=[],
        )
        result = circuit.run_spectral(100, activation='scale', scale=1.0)
        self.assertTrue(result['spectral'])
        # Counter: bias=1.0 per tick, self-coupling=1.0
        # After N ticks: state ≈ N (through tanh may saturate, so use scale=1.0)
        val = result['states']['ctr'][0]
        self.assertGreater(val, 50.0, f"Counter should accumulate, got {val}")
        print(f"\n  Spectral counter (100 ticks): {val:.1f}")
        print(f"    Per-tick: {result['per_tick_ns']:.0f}ns")
        print(f"    Coupling/tick: {result['coupling_per_tick']}")

    def test_spectral_routing(self):
        """Signals propagate through spectral routing matrix."""
        def const_fn(inp, state):
            return np.array([1.0]), state

        circuit = self.fabric.create_circuit(
            blocks=[
                {'name': 'src', 'fn': const_fn, 'state_size': 1, 'gpu_type': 'counter'},
                {'name': 'dst', 'fn': self._acc_fn, 'state_size': 1, 'gpu_type': 'accumulator'},
            ],
            connections=[
                {'from_block': 'src', 'from_port': 0, 'to_block': 'dst', 'to_port': 0},
            ],
        )
        result = circuit.run_spectral(20, activation='tanh')

        # src should have accumulated (counter), dst should have received signal
        src_val = result['states']['src'][0]
        dst_val = result['states']['dst'][0]
        # Both should be non-zero (signal propagated)
        self.assertNotAlmostEqual(dst_val, 0.0, places=2,
            msg="Destination should receive signal from source via routing matrix")
        print(f"\n  Spectral routing: src={src_val:.4f}, dst={dst_val:.4f}")

    def test_spectral_capacity_scaling(self):
        """Run circuits at increasing scale: 10, 50, 100, 500 blocks."""
        print(f"\n  Spectral Capacity Scaling:")
        print(f"    {'Blocks':>8}  {'Couplings':>12}  {'Total':>10}  {'Per-tick':>12}")

        for n_blocks in [10, 50, 100, 500]:
            blocks = [{'name': f'b{i}', 'fn': self._counter_fn, 'state_size': 1,
                       'gpu_type': 'counter'} for i in range(n_blocks)]
            connections = [
                {'from_block': f'b{i}', 'from_port': 0,
                 'to_block': f'b{(i+1)%n_blocks}', 'to_port': 0}
                for i in range(n_blocks)
            ]
            circuit = self.fabric.create_circuit(blocks, connections)
            result = circuit.run_spectral(100, activation='tanh')

            print(f"    {n_blocks:>8}  {result['coupling_per_tick']:>12,}  "
                  f"{result['compute_ms']:>9.2f}ms  {result['per_tick_ns']:>10.0f}ns")

    def test_spectral_vs_discrete_speedup(self):
        """Compare spectral vs discrete GPU fused for 10-block circuit."""
        blocks = [{'name': f'b{i}', 'fn': self._counter_fn, 'state_size': 1,
                   'gpu_type': 'counter'} for i in range(10)]
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%10}', 'to_port': 0}
            for i in range(10)
        ]

        n = 100

        # Discrete GPU fused
        c1 = self.fabric.create_circuit(blocks, connections)
        r1 = c1.run_fused_gpu(n)

        # Spectral
        c2 = self.fabric.create_circuit(blocks, connections)
        r2 = c2.run_spectral(n, activation='tanh')

        ratio = r1['per_tick_ns'] / max(r2['per_tick_ns'], 1)
        print(f"\n  Spectral vs Discrete GPU (10 blocks × {n} ticks):")
        print(f"    Discrete GPU: {r1['per_tick_ns']:.0f}ns/tick")
        print(f"    Spectral:     {r2['per_tick_ns']:.0f}ns/tick")
        print(f"    Ratio: {ratio:.2f}x")

    def test_spectral_1000_blocks(self):
        """1000-block circuit — impossible with discrete storageBarrier cost."""
        blocks = [{'name': f'b{i}', 'fn': self._counter_fn, 'state_size': 1,
                   'gpu_type': 'counter'} for i in range(1000)]
        # Ring topology
        connections = [
            {'from_block': f'b{i}', 'from_port': 0,
             'to_block': f'b{(i+1)%1000}', 'to_port': 0}
            for i in range(1000)
        ]
        circuit = self.fabric.create_circuit(blocks, connections)
        result = circuit.run_spectral(10, activation='tanh')

        self.assertEqual(result['coupling_per_tick'], 1000 * 1000)
        print(f"\n  1000-block spectral circuit:")
        print(f"    Coupling/tick: {result['coupling_per_tick']:,}")
        print(f"    Total: {result['compute_ms']:.2f}ms for 10 ticks")
        print(f"    Per-tick: {result['per_tick_ns']:.0f}ns")
        print(f"    Info density: 1M couplings/tick in one matmul")

    def test_spectral_information_density(self):
        """Verify O(D²) coupling per tick — the Virtual GPU expansion."""
        for D in [10, 64, 256, 512]:
            blocks = [{'name': f'b{i}', 'fn': self._counter_fn, 'state_size': 1,
                       'gpu_type': 'counter'} for i in range(D)]
            connections = [
                {'from_block': f'b{i}', 'from_port': 0,
                 'to_block': f'b{(i+1)%D}', 'to_port': 0}
                for i in range(D)
            ]
            circuit = self.fabric.create_circuit(blocks, connections)
            result = circuit.run_spectral(1, activation='tanh')
            self.assertEqual(result['coupling_per_tick'], D * D)

        print(f"\n  Information density verified: D² couplings per tick")
        print(f"    D=10: {100:,} couplings")
        print(f"    D=64: {4096:,} couplings")
        print(f"    D=256: {65536:,} couplings")
        print(f"    D=512: {262144:,} couplings")


if __name__ == "__main__":
    unittest.main(verbosity=2)
