Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- **New demo `demos/larql_polysemantic_12/demo.py`** — runs 12 independent single-query Qiskit simulations at 1024 shots each and recovers the analytic polysemy scores (≈ 75% on in-feature concepts, ≈ 33.3% cross-talk floor on out-of-feature) within Monte-Carlo tolerance.
- **Resource estimation** — new `## resources` section declares which static cost numbers the compiler should report (`gate_count`, `depth`, `cx_count`, `t_count`, `logical_qubits`); `## invariants` accepts the same five identifiers as bound LHS so a budget overrun becomes a verifier error (`RESOURCE_BOUND_EXCEEDED`) before any hardware run. Two new compiler entry points — `q_orca.estimate_resources(machine)` and `q_orca.compile_with_resources(machine)` — share one transpile pass per machine via `id(machine)` memoization. Verifier stage 4c (`resource_bounds`) is gated on the presence of resource invariants and can be skipped via `--skip-resource-bounds` or `VerifyOptions(skip_resource_bounds=True)`. Examples `bell-entangler`, `qaoa-maxcut`, and `vqe-heisenberg` pin their gate budgets. See `docs/language/resources.md`.
- **Structured-overlap polysemantic example, demo, and Gram helper** — companion to `larql-polysemantic-12` that swaps the flat Hadamard dictionary for a *clustered* concept geometry on a compact 3-qubit register. `examples/larql-polysemantic-clusters.q.orca.md` encodes 12 concepts in 3 clusters of 4 (capitals, fruits, vehicles) via a multi-angle parametric `prepare_concept(a, b, c)` + `query_concept(a, b, c)` pair (intra-cluster overlap 0.72 uniform, inter-cluster < 0.10 — a block-structured Gram matrix). `demos/larql_polysemantic_clusters/demo.py` prints an ASCII Gram heatmap and recovers the three-tier polysemy column `1.0 / 0.72 / ≲ 0.09` empirically. New optional helper `q_orca.compute_concept_gram(machine, concept_action_label="query_concept")` returns the analytic N×N overlap matrix for any machine that follows the product-state preparation convention; raises `ConceptGramConfigurationError` when the convention is violated.
- **Hierarchical (MPS bond-2) polysemantic example, demo, and Gram helper** — rung-1 sibling of `larql-polysemantic-clusters` that lifts the 12-concept dictionary from product states to **bond-dimension-2 matrix product states** via a CNOT-staircase preparation `Ry(q0,α); CNOT(q0,q1); Ry(q1,β); CNOT(q1,q2); Ry(q2,γ)`. `examples/larql-polysemantic-hierarchical.q.orca.md` organizes 12 concepts as a two-level hierarchy (3 super-groups × 2 sub-clusters × 2 concepts) on the same 3-qubit register and produces a four-tier Gram matrix — self 1.000 / sub-cluster-mate 0.882 / super-group-sibling [0.47, 0.54] / cross-group [0.12, 0.25] — one tier richer than rung 0. `demos/larql_polysemantic_hierarchical/demo.py` prints a 4-tier ASCII Gram heatmap, recovers the polysemy column from 12 Qiskit circuits, and prints a side-by-side rung-0 vs rung-1 comparison. New optional helper `q_orca.compute_concept_gram_mps(machine, concept_action_label="query_concept", bond_dim=2)` enumerates the parametric call sites of a CNOT-staircase action, builds each concept statevector, and returns the analytic N×N overlap matrix; raises `MpsGramConfigurationError` (missing action, wrong signature, non-staircase effect, no call sites, or `bond_dim != 2`).

### Changed

Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ q-orca simulate examples/bell-entangler.q.orca.md --run --json
| `larql-polysemantic-2.q.orca.md` | Concept projection over 2 non-orthogonal LARQL features (minimum mechanism) |
| `larql-polysemantic-12.q.orca.md` | 12-call-site parametric concept projection with uniform-overlap dictionary |
| `larql-polysemantic-clusters.q.orca.md` | Block-structured 12-concept polysemy on a 3-qubit register (3 clusters of 4) |
| `larql-polysemantic-hierarchical.q.orca.md` | Hierarchical 12-concept polysemy via bond-2 MPS encoding (3 super-groups × 2 sub-clusters × 2) |
| `larql-gate-knn-grover.q.orca.md` | Grover-amplified gate-KNN lookup (per-layer kernel of LARQL inference) |

### Hybrid Classical + Quantum Demo
Expand Down Expand Up @@ -587,6 +588,32 @@ parameter, one action template); use `larql-polysemantic-clusters` when
you want the clustered-phenomenon signature on top of the same parametric
mechanism.

#### Hierarchical polysemy

The clusters example above is the rung-0 polysemantic encoding — concepts
prepared as **product states** on the 3-qubit register, producing three
flat overlap tiers (self / cluster-mate / cross-cluster). Real
sparse-autoencoder dictionaries report *graded* within-cluster similarity
and **sub-cluster structure** that flat block tiers cannot express.
[`examples/larql-polysemantic-hierarchical.q.orca.md`](examples/larql-polysemantic-hierarchical.q.orca.md)
lifts the encoding to **bond-dimension-2 matrix product states** via a
`Ry(q0,α); CNOT(q0,q1); Ry(q1,β); CNOT(q1,q2); Ry(q2,γ)` staircase. The
12 concepts are organized as a two-level hierarchy — 3 super-groups
(`animals`, `fruits`, `vehicles`) × 2 sub-clusters × 2 concepts — and the
Gram matrix splits into **four** ordered tiers: self 1.000 /
sub-cluster-mate 0.882 / super-group-sibling [0.47, 0.54] / cross-group
[0.12, 0.25].

The optional `q_orca.compiler.concept_gram_mps.compute_concept_gram_mps(machine)`
helper produces the Gram matrix for machines following the CNOT-staircase
convention (parallel to `compute_concept_gram` for product-state
machines). See [`demos/larql_polysemantic_hierarchical/demo.py`](demos/larql_polysemantic_hierarchical/demo.py)
for a run that prints the 4-tier Gram heatmap, recovers the polysemy
column from 12 Qiskit circuits, and prints a side-by-side rung-0 vs
rung-1 comparison. Background and the full ansatz ladder
(rungs 0–3) are in
[`docs/research/polysemantic-encoding-beyond-product-states.md`](docs/research/polysemantic-encoding-beyond-product-states.md).

---

### Verify output (5-stage pipeline)
Expand Down
291 changes: 291 additions & 0 deletions demos/larql_polysemantic_hierarchical/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
"""LARQL polysemantic-hierarchical demo (rung-1, MPS bond-2 encoding).

End-to-end validation of the `LarqlPolysemanticHierarchical` state
machine, the rung-1 sibling of `larql_polysemantic_clusters`:

1. parse + verify the .q.orca.md (5-stage pipeline)
2. compile to Mermaid + OpenQASM 3.0 + Qiskit
3. analytic Gram matrix via `compute_concept_gram_mps` — visualized
as a 4-tier ASCII heatmap showing the two-level hierarchy
4. for each of the 12 concepts, run a fresh prepare+query circuit
(feature = |dog>) and recover the polysemy column
5. compare empirical vs. analytic polysemy columns; pass/fail on
|max_error| < 3 · mc_std
6. side-by-side recap of rung-0 (flat 3-tier) vs. rung-1 (hierarchical
4-tier) Gram signatures.

Topology: 3 super-groups × 2 sub-clusters × 2 concepts = 12 concepts on a
3-qubit register. Each concept is encoded as the bond-2 MPS

|c_i> = Ry(q0, α_i) CNOT(q0, q1) Ry(q1, β_i) CNOT(q1, q2) Ry(q2, γ_i) |000>

with α ∈ {0, 2π/3, 4π/3} (super-group), β ∈ {-0.75, +0.75} (sub-cluster),
and γ ∈ {-0.35, +0.35} (concept). This produces analytic
`|<c_i|c_j>|²` tiers — self 1.0, sub-cluster-mate 0.882, super-group-
sibling [0.47, 0.54], cross-group [0.12, 0.25] — distinct from the
flat-block clusters demo's three uniform tiers.

Usage:
pip install q-orca[quantum]
python demos/larql_polysemantic_hierarchical/demo.py
"""

from __future__ import annotations

from pathlib import Path

import numpy as np

from q_orca import (
QSimulationOptions,
VerifyOptions,
compile_to_mermaid,
compile_to_qasm,
compile_to_qiskit,
compute_concept_gram_mps,
parse_q_orca_markdown,
verify,
)

REPO_ROOT = Path(__file__).resolve().parents[2]
MACHINE_PATH = REPO_ROOT / "examples" / "larql-polysemantic-hierarchical.q.orca.md"

# (concept_name, super_group, sub_cluster) — order matches transition order.
CONCEPTS = [
("dog", "animals", "mammals"),
("cat", "animals", "mammals"),
("robin", "animals", "birds"),
("eagle", "animals", "birds"),
("strawberry", "fruits", "berries"),
("blueberry", "fruits", "berries"),
("mango", "fruits", "tropical"),
("papaya", "fruits", "tropical"),
("car", "vehicles", "land"),
("bike", "vehicles", "land"),
("plane", "vehicles", "air"),
("drone", "vehicles", "air"),
]
FEATURE_INDEX = 0 # |f> = |dog>
SHOTS = 1024


def banner(title: str) -> None:
bar = "=" * 72
print(f"\n{bar}\n {title}\n{bar}")


def heatmap_tier(value: float) -> str:
"""4-tier ASCII heatmap: '#' ≥ 0.7, 'o' ∈ [0.3, 0.7), '.' ∈ [0.1, 0.3), blank < 0.1."""
v = abs(value)
if v >= 0.7:
return "#"
if v >= 0.3:
return "o"
if v >= 0.1:
return "."
return " "


def print_gram_heatmap(gram: np.ndarray) -> None:
"""Print |gram|² as a 4-tier 12×12 ASCII heatmap with hierarchy labels."""
gsq = np.abs(gram) ** 2
print(" |gram[i,j]|² (# ≥ 0.7, o ∈ [0.3, 0.7), . ∈ [0.1, 0.3), blank < 0.1)")
print(" ", "".join(f"{i:>3}" for i in range(12)))
for i in range(12):
row = "".join(f" {heatmap_tier(gsq[i, j])}" for j in range(12))
name, sup, sub = CONCEPTS[i]
print(f" {i:>2} {row} ({name}, {sup}/{sub})")


def build_query_circuit(prepare_angles: tuple, query_angles: tuple):
"""Build a prepare(feature) + query(concept) circuit on 3 qubits.

Mirrors the .q.orca.md effect strings exactly:
prepare: Ry(q0, a); CNOT(q0,q1); Ry(q1, b); CNOT(q1,q2); Ry(q2, c)
query: Ry(q2,-c); CNOT(q1,q2); Ry(q1,-b); CNOT(q0,q1); Ry(q0,-a)
"""
from qiskit import QuantumCircuit

qc = QuantumCircuit(3, 3)
a, b, c = prepare_angles
qc.ry(a, 0)
qc.cx(0, 1)
qc.ry(b, 1)
qc.cx(1, 2)
qc.ry(c, 2)
a2, b2, c2 = query_angles
qc.ry(-c2, 2)
qc.cx(1, 2)
qc.ry(-b2, 1)
qc.cx(0, 1)
qc.ry(-a2, 0)
qc.measure(range(3), range(3))
return qc


def run_one_query(prepare_angles: tuple, query_angles: tuple, shots: int) -> float:
"""Return empirical P(|000>) from `shots` shots."""
from qiskit import transpile
from qiskit.providers.basic_provider import BasicSimulator

qc = build_query_circuit(prepare_angles, query_angles)
backend = BasicSimulator()
tqc = transpile(qc, backend, basis_gates=["u3", "cx", "id"])
result = backend.run(tqc, shots=shots).result()
counts = result.get_counts()
hit = counts.get("000", 0)
total = sum(counts.values())
return hit / total if total else 0.0


def main() -> None:
banner("LARQL polysemantic-hierarchical demo (rung-1, MPS bond-2)")
print(f" Machine source : {MACHINE_PATH.relative_to(REPO_ROOT)}")
print(f" Concepts : {len(CONCEPTS)} over 3 super-groups (animals, fruits, vehicles)")
print(" × 2 sub-clusters × 2 concepts (12 total)")
print(f" Feature loaded : |f> = |{CONCEPTS[FEATURE_INDEX][0]}>")
print(f" Shots / query : {SHOTS}")

# 1. Parse + verify
banner("1. Verify state machine (Q-Orca pipeline)")
source = MACHINE_PATH.read_text()
parsed = parse_q_orca_markdown(source)
if parsed.errors:
for e in parsed.errors:
print(f" parse error: {e}")
raise SystemExit(1)
machine = parsed.file.machines[0]
print(f" Machine name : {machine.name}")
print(f" States : {len(machine.states)}")
print(f" Transitions : {len(machine.transitions)}")
parametric = [
(a.name, [(p.name, p.type) for p in a.parameters])
for a in machine.actions
if a.parameters
]
print(f" Actions : {parametric}")

call_sites = [t for t in machine.transitions if t.action == "query_concept"]
print(f" query_concept call sites : {len(call_sites)}")

result = verify(machine, VerifyOptions(skip_dynamic=True))
status = "VALID" if result.valid else "INVALID"
print(f" Verification : {status}")
err_count = sum(1 for e in result.errors if e.severity == "error")
warn_count = sum(1 for e in result.errors if e.severity == "warning")
print(f" Errors / warnings : {err_count} / {warn_count}")

# 2. Compile
banner("2. Compile to Mermaid + OpenQASM 3.0 + Qiskit")
mermaid = compile_to_mermaid(machine)
qasm = compile_to_qasm(machine)
qiskit_script = compile_to_qiskit(
machine,
QSimulationOptions(analytic=False, shots=0, run=False, skip_qutip=True),
)
print(f" Mermaid lines : {len(mermaid.strip().splitlines())}")
print(f" QASM lines : {len(qasm.strip().splitlines())}")
print(f" Qiskit lines : {len(qiskit_script.strip().splitlines())}")
print(f" QASM register : {'qubit[3] q;' in qasm}")
print(
f" qc.ry calls : {qiskit_script.count('qc.ry(')} "
f"(3 ry × 13 transitions = 39)"
)
print(
f" qc.cx calls : {qiskit_script.count('qc.cx(')} "
f"(2 cx × 13 transitions = 26)"
)

# 3. Analytic Gram matrix (4-tier hierarchy)
banner("3. Analytic Gram matrix (4-tier hierarchy)")
gram = compute_concept_gram_mps(machine)
print_gram_heatmap(gram)

gsq = np.abs(gram) ** 2
sub_mate, super_sib, cross = [], [], []
for i in range(12):
gi, si = i // 4, (i // 2) % 2
for j in range(i + 1, 12):
gj, sj = j // 4, (j // 2) % 2
v = gsq[i, j]
if gi == gj and si == sj:
sub_mate.append(v)
elif gi == gj:
super_sib.append(v)
else:
cross.append(v)
print()
print(
f" sub-cluster-mate |<c_i|c_j>|² : min={min(sub_mate):.4f} "
f"max={max(sub_mate):.4f} (n={len(sub_mate)}; analytic ≈ 0.882)"
)
print(
f" super-group-sib |<c_i|c_j>|² : min={min(super_sib):.4f} "
f"max={max(super_sib):.4f} (n={len(super_sib)}; analytic [0.47, 0.54])"
)
print(
f" cross-group |<c_i|c_j>|² : min={min(cross):.4f} "
f"max={max(cross):.4f} (n={len(cross)}; analytic [0.12, 0.25])"
)

# 4. Per-concept polysemy column (|f> = |dog>)
banner("4. Per-concept polysemy column (12 independent circuits)")
feature_angles = tuple(
float(b.value) for b in call_sites[FEATURE_INDEX].bound_arguments
)
print(f" Feature |f> prepare angles : {feature_angles}")
print()
print(
f" {'i':>2} {'concept':<11} {'group':<9} {'sub':<9} "
f"{'P(|000>) empirical':<22} {'analytic':<9} {'|err|':<7}"
)
print(
f" {'':>2} {'':-<11} {'':-<9} {'':-<9} {'':-<22} "
f"{'':-<9} {'':-<7}"
)

errors = []
for i, t in enumerate(call_sites):
query_angles = tuple(float(b.value) for b in t.bound_arguments)
p_emp = run_one_query(feature_angles, query_angles, shots=SHOTS)
p_ana = float(gsq[FEATURE_INDEX, i])
err = abs(p_emp - p_ana)
errors.append(err)
bar = "#" * int(p_emp * 20)
name, sup, sub = CONCEPTS[i]
print(
f" {i:>2} {name:<11} {sup:<9} {sub:<9} "
f"{p_emp:>6.3f} {bar:<14} {p_ana:>6.3f} {err:>6.3f}"
)

# 5. Pass/fail
banner("5. Empirical vs. analytic agreement")
max_err = max(errors)
mc_std_bound = (0.5 * 0.5 / SHOTS) ** 0.5
threshold = 3 * mc_std_bound
passed = max_err < threshold
print(f" max |empirical − analytic| across 12 concepts : {max_err:.4f}")
print(f" Monte-Carlo std bound (p=0.5, N={SHOTS}) : {mc_std_bound:.4f}")
print(f" 3·std threshold : {threshold:.4f}")
print(f" Result : {'PASS' if passed else 'FAIL'}")

# 6. Rung-0 vs. rung-1 recap
banner("6. Rung-0 (flat) vs. rung-1 (hierarchical) Gram signatures")
print(" rung-0 (larql-polysemantic-clusters, product state):")
print(" |c_i> = Ry(q0,α) Ry(q1,β) Ry(q2,γ) |000>")
print(" tiers : 1.000 / 0.720 / ≲ 0.09")
print(" (self / cluster-mate / cross-cluster — three flat tiers)")
print()
print(" rung-1 (this demo, MPS bond-2 CNOT staircase):")
print(" |c_i> = Ry(q0,α) CNOT(q0,q1) Ry(q1,β) CNOT(q1,q2) Ry(q2,γ) |000>")
print(" tiers : 1.000 / 0.882 / [0.47, 0.54] / [0.12, 0.25]")
print(" (self / sub-mate / super-sib / cross-group — four ordered tiers)")
print()
print(" The CNOT staircase entangles adjacent qubits, lifting the")
print(" block diagonal of rung-0 into a graded two-level hierarchy.")

raise SystemExit(0 if passed else 1)


if __name__ == "__main__":
main()
Loading
Loading