"""
The proposal, correctly reasoned: broadcast x optically once, fan out to K operators,
AGGREGATE IN THE ANALOG DOMAIN, then digitise one small vector instead of K large ones.

    DAC -> [P_1 ... P_K] -> optical/analog aggregation -> ADC

That is the right objective. It also collapses, and here is the algebra.
"""
import numpy as np
rng = np.random.default_rng(0)

print("=" * 78)
print("1. FIXED-GATE PRE-ADC AGGREGATION PROVABLY COLLAPSES TO ONE MATRIX")
print("=" * 78)
print("""
  If each optical operator is a linear transform P_i(x) = W_i x, and the analog
  aggregation applies FIXED weights g_i, then

      y = sum_i g_i P_i(x) = sum_i g_i W_i x = ( sum_i g_i W_i ) x = W_eff x

  The bank of K experts is EXACTLY equivalent to one precomputed matrix W_eff.
""")
K, d = 100, 64
W = rng.normal(size=(K, d, d)) / np.sqrt(d)
g = rng.normal(size=K)
x = rng.normal(size=d)
bank = sum(g[i] * (W[i] @ x) for i in range(K))
W_eff = np.tensordot(g, W, axes=(0, 0))
print(f"  K={K} experts, d={d}")
print(f"  || bank_output - W_eff @ x ||  =  {np.linalg.norm(bank - W_eff @ x):.3e}   <- zero")
print(f"  hardware for the bank : {K} optical operators ({K*d*d:,} weights)")
print(f"  hardware for W_eff    : 1 optical operator ({d*d:,} weights)")
print(f"  -> the bank costs {K}x the hardware for PROVABLY IDENTICAL function.")

print("""
  Note this is exactly what the NSR 192-channel chip does -- and correctly so: it
  combines channels into ONE convolution kernel. A kernel IS a single linear operator.
  The demonstrated chip is the collapsed case, which is fine because a convolution is
  what it wanted. It is not a mixture of experts, and it cannot become one for free.
""")

print("=" * 78)
print("2. THE TRILEMMA")
print("=" * 78)
print("""
  A bank of K experts is only worth its hardware if the gates g_i DEPEND ON x
  (routing) or the P_i are NONLINEAR. Each escape has a price already established:

  (a) g fixed, P_i linear
        -> collapses to one matrix (proved above). ZERO benefit, Kx hardware.

  (b) g_i = g_i(x)  -- real input-dependent routing
        -> the routing decision is INFORMATION that must be computed from x and applied
           to the analog datapath. Either:
             * compute it digitally  -> you must detect something per expert FIRST,
               which is the K-conversion cost the pre-ADC aggregation existed to avoid;
             * or apply it optically -> the analog weights now change every token, which
               is dynamic_weights=True in costmodel/weights.py, whose docstring already
               prices reprogramming as "orders of magnitude more than the whole
               computation is worth optically".

  (c) P_i nonlinear before aggregation
        -> requires nonlinearity INSIDE the optical domain, between broadcast and
           aggregation. That is the cascadability wall (costmodel/cascadability.py):
           loss and noise accumulate, forcing O-E-O regeneration. |E|^2 at the
           photodetector does not help -- it lives AT the boundary, not before it.

  So: AMORTISED CONVERSIONS, INPUT-DEPENDENT ROUTING -- pick one.
""")

print("=" * 78)
print("3. WHY: ROUTING IS INFORMATION, AND IT HAS TO CROSS THE BOUNDARY")
print("=" * 78)
print("""
  The pre-ADC aggregation wins by NOT moving K vectors across the domain boundary.
  But a router's decision is a function OF those vectors. You cannot select among
  things you have not measured. The saving and the selection are the same bits.

  This is the third time this session that two proposed wins turn out to be one
  resource counted twice:
    * sparsity vs weight-residency  (333x saving <-> 333x penalty)
    * hierarchy levels in compression  ((k2/k1)x(N/k2) = N/k1, middle cancels)
    * pre-ADC aggregation vs input-dependent routing  (this)
  Check 3 of docs/SUBSTRATE-GATE.md: name the resource each half saves.
""")
