quantum-suite / app.py
AEUPH's picture
Master consolidation: Quantum Suite
88d755f verified
Raw History Blame Contribute Delete
19.3 kB
"""Quantum Suite — a real statevector quantum simulator in ONE cpu-basic Space.
Consolidates the 11-member `QUANTUM_*` family, none of which can run on a free
CPU Space: every one of them imports `qiskit`, `torch` and `transformers` and pulls
real models (GPT-2, facebook/bart-large-mnli, the Olivetti faces set) at import time.
This suite replaces them with the physics that actually matters and nothing else:
a hand-written complex-amplitude simulator (pure Python + numpy), so the whole
family fits in one slot.
Tabs
----
Circuit Simulator a text circuit language, amplitudes and probabilities out
Superposition Explorer the uniform superposition and its 1/sqrt(N) statistics
Grover Search real amplitude amplification, iteration by iteration
Entanglement Lab Bell/GHZ states, entropy, and a CHSH bound
Family Index what this suite consolidates, and why
Statevector convention: qubit `q` is bit `n-1-q` of the basis index, so qubit 0 is
the most significant bit and two-qubit operators are the usual `kron(A, B)` form.
"""
import math
import numpy as np
import gradio as gr
# ---------------------------------------------------------------- gates
H = np.array([[1, 1], [1, -1]], dtype=complex) / math.sqrt(2)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
S = np.array([[1, 0], [0, 1j]], dtype=complex)
T = np.array([[1, 0], [0, np.exp(1j * math.pi / 4)]], dtype=complex)
ONE_QUBIT = {"H": H, "X": X, "Y": Y, "Z": Z, "S": S, "T": T}
ROTATIONS = ("RX", "RY", "RZ")
# two-qubit unitaries in the (control/target) = (a, b) index order, row = 2*a + b
CX = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=complex)
CZ = np.diag([1, 1, 1, -1]).astype(complex)
SWAP = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], dtype=complex)
TWO_QUBIT = {"CX": CX, "CNOT": CX, "CZ": CZ, "SWAP": SWAP}
def rotation(name, theta):
c, s = math.cos(theta / 2), math.sin(theta / 2)
if name == "RX":
return np.array([[c, -1j * s], [-1j * s, c]], dtype=complex)
if name == "RY":
return np.array([[c, -s], [s, c]], dtype=complex)
return np.diag([np.exp(-1j * theta / 2), np.exp(1j * theta / 2)]).astype(complex)
# ---------------------------------------------------------------- simulator
def apply_one(state, U, q, n):
"""Apply a 1-qubit unitary to qubit `q` by moving its axis to the front."""
a = np.moveaxis(state.reshape([2] * n), n - 1 - q, 0)
a = np.tensordot(U, a, axes=([1], [0]))
return np.moveaxis(a, 0, n - 1 - q).reshape(-1)
def apply_two(state, U, qa, qb, n):
"""Apply a 2-qubit unitary; qa/qb are reordered so the matrix index is 2*a + b."""
axa, axb = n - 1 - qa, n - 1 - qb
a = np.moveaxis(state.reshape([2] * n), [axa, axb], [0, 1])
a = (U @ a.reshape(4, -1)).reshape(a.shape)
return np.moveaxis(a, [0, 1], [axa, axb]).reshape(-1)
def parse_circuit(text, n=None):
"""Parse `H 0` / `RZ 1 0.785` / `CX 0 1` lines. `*` means every qubit."""
lines = []
for raw in (text or "").splitlines():
line = raw.split("#", 1)[0].strip()
if line:
lines.append(line.split())
if not lines:
raise ValueError("empty circuit — give at least one gate")
qubits = []
for parts in lines:
gate = parts[0].upper()
if gate in ONE_QUBIT or gate in ROTATIONS:
if len(parts) < 2:
raise ValueError(f"'{parts[0]}' needs a qubit, e.g. '{gate} 0'")
if parts[1] != "*":
qubits.append(int(parts[1]))
elif gate in TWO_QUBIT:
if len(parts) < 3:
raise ValueError(f"'{parts[0]}' needs two qubits, e.g. '{gate} 0 1'")
qubits += [int(parts[1]), int(parts[2])]
else:
raise ValueError(f"unknown gate '{parts[0]}'")
if not qubits:
# only possible when every gate targets `*`, which cannot imply a width
if not n:
raise ValueError("a circuit that only uses '*' needs the qubit count filled in")
n = int(n)
else:
n = int(n) if n else (max(qubits) + 1)
if n < 1 or n > 12:
raise ValueError("qubit count must be between 1 and 12")
if qubits and max(qubits) >= n:
raise ValueError(f"circuit uses qubit {max(qubits)} but n={n}")
return lines, n
def simulate(text, n=None):
"""Run a circuit; return (final state, parsed lines, n)."""
lines, n = parse_circuit(text, n)
state = np.zeros(2 ** n, dtype=complex)
state[0] = 1.0
for parts in lines:
gate = parts[0].upper()
if gate in ONE_QUBIT:
targets = range(n) if parts[1] == "*" else [int(parts[1])]
for q in targets:
state = apply_one(state, ONE_QUBIT[gate], q, n)
elif gate in ROTATIONS:
if len(parts) < 3:
raise ValueError(f"'{gate}' needs a qubit and an angle, e.g. '{gate} 0 0.785'")
theta = float(parts[2])
targets = range(n) if parts[1] == "*" else [int(parts[1])]
U = rotation(gate, theta)
for q in targets:
state = apply_one(state, U, q, n)
else:
state = apply_two(state, TWO_QUBIT[gate], int(parts[1]), int(parts[2]), n)
state = state / np.linalg.norm(state) # guards against accumulated round-off
return state, lines, n
def basis_label(i, n):
return "|" + format(int(i), f"0{n}b") + ">"
def fmt_complex(z):
return f"{z.real:+.4f}{z.imag:+.4f}i"
def z_expectations(state, n):
probs = np.abs(state) ** 2
out = []
for q in range(n):
sign = np.array([1.0 if not ((i >> (n - 1 - q)) & 1) else -1.0 for i in range(2 ** n)])
out.append(float(np.sum(sign * probs)))
return out
# ---------------------------------------------------------------- tab 1
DEFAULT_CIRCUIT = """H 0
CX 0 1
RZ 1 0.785
H 1
CX 0 2"""
def circuit_sim(text, n_qubits):
try:
state, lines, n = simulate(text, n_qubits or None)
except (ValueError, TypeError) as e:
return f"Circuit error: {e}", "", ""
probs = np.abs(state) ** 2
head = [
f"{len(lines)} gates on {n} qubits |state| = {np.linalg.norm(state):.12f}",
f"non-zero amplitudes: {int(np.sum(probs > 1e-12))} of {2 ** n}",
"",
"probabilities",
]
head += [f" {basis_label(i, n):>12} {probs[i]:.6f}" for i in np.argsort(-probs) if probs[i] > 1e-9]
amps = ["amplitudes (top 16 by probability)"]
for i in np.argsort(-probs)[:16]:
if probs[i] > 1e-12:
amps.append(f" {basis_label(i, n):>12} {fmt_complex(state[i])} p = {probs[i]:.6f}")
zs = z_expectations(state, n)
zs_out = ["<Z_q> per qubit (-1 / +1 is a definite bit, 0 is full superposition)"]
zs_out += [f" q{q}: {v:+.6f}" for q, v in enumerate(zs)]
return "\n".join(head), "\n".join(amps), "\n".join(zs_out)
INTRO_SIM = ("A text circuit language (`H 0`, `CX 0 1`, `RZ 1 0.785`, `H *` for all qubits) "
"run on a hand-written statevector simulator. CPU only, no qiskit.")
# ---------------------------------------------------------------- tab 2
def superposition(n_qubits, shots):
n = int(n_qubits)
state, _, n = simulate("\n".join(f"H {q}" for q in range(n)), n)
probs = np.abs(state) ** 2
rng = np.random.default_rng(0)
counts = np.bincount(rng.choice(2 ** n, size=int(shots), p=probs), minlength=2 ** n)
N = 2 ** n
rows = [
f"n = {n} qubits -> N = {N} basis states, each with amplitude 1/sqrt(N) = {1 / math.sqrt(N):.6f}",
f"max |p - 1/N| over all states = {np.max(np.abs(probs - 1 / N)):.3e}",
"",
f"{'state':>12} {'amplitude':>22} {'p':>10} {'count/'+str(int(shots)):>12} freq",
]
for i in range(min(N, 16)):
rows.append(f"{basis_label(i, n):>12} {fmt_complex(state[i]):>22} {probs[i]:>10.6f} "
f"{counts[i]:>12} {counts[i] / shots:.4f}")
if N > 16:
rows.append(f" ... {N - 16} more states, all with the same probability")
rows += [
"",
"why the sqrt(N) matters",
f" a single measurement identifies one of {N} outcomes: P(correct) = 1/N = {1 / N:.6f}",
f" the standard error of the estimate falls as 1/sqrt(N) = {1 / math.sqrt(N):.6f} per shot",
f" to reach a fixed confidence you need O(N) shots classically — see Grover Search "
f"for the quantum route at O(sqrt(N))",
]
return "\n".join(rows)
INTRO_SUPER = ("N = 2^n basis states carried at once, with the uniform amplitude 1/sqrt(N) "
"verified state by state against seeded sampling.")
# ---------------------------------------------------------------- tab 3
def grover(n_qubits, marked):
n = int(n_qubits)
N = 2 ** n
marked = int(marked)
if not 0 <= marked < N:
return f"The marked item must be between 0 and {N - 1} for n = {n}."
state = np.ones(N, dtype=complex) / math.sqrt(N)
optimal = max(1, int(round(math.pi / 4 * math.sqrt(N))))
rows = [
f"search space N = {N} marked item = {basis_label(marked, n)}",
f"classical search: {N // 2} trials on average. Grover: about pi/4 * sqrt(N) = "
f"{math.pi / 4 * math.sqrt(N):.2f} iterations.",
"",
f"{'iteration':>9} {'P(marked)':>12} {'amplitude(marked)':>20} {'max other p':>12}",
]
p = float(abs(state[marked]) ** 2)
rows.append(f"{0:>9} {p:>12.6f} {fmt_complex(state[marked]):>20} "
f"{float(np.max(np.delete(np.abs(state) ** 2, marked))):>12.6f}")
for it in range(1, optimal + 2):
state = state.copy()
state[marked] *= -1.0 # oracle: phase flip the target
state = 2 * np.mean(state) * np.ones(N, dtype=complex) - state # diffusion: 2|s><s| - I
p = float(abs(state[marked]) ** 2)
others = float(np.max(np.delete(np.abs(state) ** 2, marked)))
rows.append(f"{it:>9} {p:>12.6f} {fmt_complex(state[marked]):>20} {others:>12.6f}")
if p > 0.999:
break
rows += [
"",
"amplitude amplification: the oracle flips the sign of the marked amplitude, the "
"diffusion mirrors every amplitude about the mean. Both steps together rotate the "
"state toward the marked item by a fixed angle, so the success probability rises "
"as sin^2((2k+1) theta) with sin(theta) = 1/sqrt(N).",
"past the optimal iteration count the same rotation carries the state away again — "
"the table shows the rise, and the peak is the shortest route to the answer.",
]
return "\n".join(rows)
INTRO_GROVER = ("Grover's algorithm run for real on the simulator: the oracle and the "
"diffusion operator applied iteration by iteration, with the success "
"probability after each one.")
# ---------------------------------------------------------------- tab 4
def reduced_density(state, q, n):
a = np.moveaxis(state.reshape([2] * n), n - 1 - q, 0).reshape(2, -1)
return a @ a.conj().T
def entropy_of(state, q, n):
ev = np.linalg.eigvalsh(reduced_density(state, q, n))
ev = ev[ev > 1e-12]
return float(-np.sum(ev * np.log2(ev)))
def bell_states():
out = ["Bell states, each produced by gates on |00> (not written down by hand):", ""]
setup = {"|Phi+>": "H 0\nCX 0 1", "|Phi->": "H 0\nCX 0 1\nZ 0",
"|Psi+>": "H 0\nCX 0 1\nX 0", "|Psi->": "H 0\nCX 0 1\nZ 0\nX 0"}
for name, circ in setup.items():
state, _, n = simulate(circ, 2)
amps = " ".join(f"{basis_label(i, n)}={fmt_complex(state[i])}"
for i in range(4) if abs(state[i]) > 1e-9)
gates = ", ".join(circ.splitlines())
out.append(f"{name:7} -> {amps}")
out.append(f" gates: {gates}")
out.append(f" S(qubit0) = {entropy_of(state, 0, n):.4f} bits <- maximal entanglement")
out.append("")
return "\n".join(out)
def ghz_states():
out = ["Greenberger-Horne-Zeilinger states: H on qubit 0, then CNOT down the chain.", ""]
for n in (2, 3, 4):
circ = "H 0\n" + "\n".join(f"CX {q} {q + 1}" for q in range(n - 1))
state, _, n = simulate(circ, n)
probs = np.abs(state) ** 2
live = [(basis_label(i, n), probs[i]) for i in range(2 ** n) if probs[i] > 1e-9]
out.append(f"n = {n}: " + ", ".join(f"{lab} p={p:.4f}" for lab, p in live)
+ f" S(qubit0) = {entropy_of(state, 0, n):.4f} bit")
out += ["", "Only all-zeros and all-ones ever appear, however many qubits are added: "
"the whole register collapses as one object."]
return "\n".join(out)
def chsh():
"""E(a,b) = <phi| A(a) x B(b) |phi> with A(t) = cos(t) Z + sin(t) X."""
state, _, n = simulate("H 0\nCX 0 1", 2)
pairs = [("a=0", 0.0, "b=+pi/4", math.pi / 4), ("a=0", 0.0, "b'=-pi/4", -math.pi / 4),
("a'=pi/2", math.pi / 2, "b=+pi/4", math.pi / 4),
("a'=pi/2", math.pi / 2, "b'=-pi/4", -math.pi / 4)]
rows = ["CHSH correlations from the simulator, |Phi+> on |00>:",
f"{'direction pair':30} {'E(a,b)':>10}", ""]
es = []
for la, ta, lb, tb in pairs:
A = math.cos(ta) * Z + math.sin(ta) * X
B = math.cos(tb) * Z + math.sin(tb) * X
M = np.kron(A, B)
e = float(np.real(state.conj() @ M @ state))
es.append(e)
rows.append(f"{la + ' vs ' + lb:30} {e:>10.6f}")
S = es[0] + es[1] + es[2] - es[3]
rows += [
"",
f"S = E(a,b) + E(a,b') + E(a',b) - E(a',b') = {S:.6f}",
f"2 * sqrt(2) = {2 * math.sqrt(2):.6f} <- the quantum maximum (Tsirelson bound)",
"classical bound: |S| <= 2. A value above 2 cannot be produced by any local "
"hidden-variable model, which is the measured statement of Bell's theorem.",
]
return "\n".join(rows)
def entanglement_lab():
return bell_states() + "\n\n" + ghz_states() + "\n\n" + chsh()
INTRO_ENT = ("Bell and GHZ states built from gates, their entanglement entropy, and a CHSH "
"correlation table computed from the statevector — the classical bound is "
"exceeded numerically.")
# ---------------------------------------------------------------- tab 5
FAMILY = [
("QUANTUM_AI", "60 lines: qiskit + qiskit_aer + torch + transformers + sklearn"),
("QUANTUM_COGNITION", "89 lines: qiskit simulation behind a chat shell"),
("QUANTUM_COGNITION_CHAT_AI", "97 lines: qiskit + a Hugging Face pipeline at import"),
("QUANTUM_COGNITION_CHATBOT", "100 lines: same stack, model loaded at import time"),
("QUANTUM_FUZZY_CONSTRAINTS", "106 lines: qiskit behind a fuzzy-constraint demo"),
("QUANTUM_GEN", "80 lines: qiskit + generated text"),
("QUANTUM_GENERATIVE_MODEL", "79 lines: model download at import time"),
("QUANTUM_GENIE", "61 lines: qiskit + a text pipeline"),
("QUANTUM_GENIUS", "118 lines: qiskit + transformers, no CPU path"),
("QUANTUM_IMG_GEN", "55 lines: qiskit + torch + facebook/bart-large-mnli + Olivetti faces"),
("QUANTUM_SUPERPOSITION_GEN", "60 lines: qiskit + GPT-2 weights loaded at import"),
]
REASONS = ("Every member states its dependencies as `qiskit`, `qiskit_aer`, `torch`, "
"`transformers` and often `sklearn` and `matplotlib`, and several download model "
"weights (`gpt2`, `facebook/bart-large-mnli`) or a dataset (`fetch_olivetti_faces`) "
"at import time. On a free cpu-basic Space that means a multi-gigabyte image and a "
"cold start that pulls weights before the app can serve, which is why all 11 sat "
"asleep rather than running.")
def family_index():
rows = ["| consolidated Space | what blocks it on a free CPU Space |", "|---|---|"]
rows += [f"| `{name}` | {why} |" for name, why in FAMILY]
head = ["# Family Index", "",
f"**{len(FAMILY)} Spaces in the `QUANTUM_*` family**, measured on 2026-10-12. "
"They are consolidated into the four tabs above, which run in **one** cpu-basic "
"slot instead of one Space per project.", "", REASONS, "",
"The replacement keeps the quantum mechanics (superposition, interference, "
"amplitude amplification, entanglement, Bell violation) and drops the model "
"weights that made the originals unhostable. Nothing here is simulated with "
"another library: the amplitudes are computed directly.", ""]
return "\n".join(head + rows)
INTRO_FAMILY = ("The 11 `QUANTUM_*` Spaces this suite consolidates, and the measured reason "
"each one cannot run on a free cpu-basic Space.")
# ---------------------------------------------------------------- app
INTRO = {
"Circuit Simulator": INTRO_SIM,
"Superposition Explorer": INTRO_SUPER,
"Grover Search": INTRO_GROVER,
"Entanglement Lab": INTRO_ENT,
"Family Index": INTRO_FAMILY,
}
def page():
with gr.Tabs():
with gr.Tab("Circuit Simulator"):
gr.Markdown(INTRO_SIM)
with gr.Row():
code = gr.Code(label="circuit", language="python", value=DEFAULT_CIRCUIT)
nq = gr.Number(label="qubits (blank = infer from the circuit)", value=3, precision=0)
o1 = gr.Textbox(lines=22, label="probabilities")
with gr.Row():
o2 = gr.Textbox(lines=18, label="amplitudes")
o3 = gr.Textbox(lines=18, label="expectation of Z")
gr.Button("Run circuit").click(circuit_sim, [code, nq], [o1, o2, o3])
with gr.Tab("Superposition Explorer"):
gr.Markdown(INTRO_SUPER)
with gr.Row():
n2 = gr.Slider(1, 6, value=4, step=1, label="qubits")
shots = gr.Slider(256, 8192, value=4096, step=256, label="shots")
o4 = gr.Textbox(lines=26, label="uniform superposition")
gr.Button("Explore").click(superposition, [n2, shots], o4)
with gr.Tab("Grover Search"):
gr.Markdown(INTRO_GROVER)
with gr.Row():
n3 = gr.Slider(3, 6, value=4, step=1, label="qubits")
marked = gr.Number(label="marked item (integer)", value=11, precision=0)
o5 = gr.Textbox(lines=26, label="iterations")
gr.Button("Search").click(grover, [n3, marked], o5)
with gr.Tab("Entanglement Lab"):
gr.Markdown(INTRO_ENT)
o6 = gr.Textbox(lines=34, label="states, entropy and CHSH")
gr.Button("Compute").click(entanglement_lab, None, o6)
with gr.Tab("Family Index"):
gr.Markdown(family_index())
if __name__ == "__main__":
with gr.Blocks(title="Quantum Suite", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Quantum Suite\n"
"A statevector quantum simulator over a whole project family, in a "
"single cpu-basic Space. **No qiskit, no torch, no transformers, no GPU.**\n\n"
"Companion to the [GGUF Toolkit](https://huggingface.co/spaces/AEUPH/gguf-toolkit).")
page()
demo.launch()