diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9e1fb..8569f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.7.0 (2026-05-01) ### Added @@ -9,11 +9,16 @@ - **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`). +- **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 cross-coupled CNOT-staircase preparation `Ry(q0, a); CNOT(q0, q1); Ry(q1, a + b); CNOT(q1, q2); Ry(q2, b + c)` (linear-combination angles cross-couple adjacent qubits, producing a Gram that does not factorize as `|⟨φᵢ|φⱼ⟩|² = ∏ₖ |⟨φᵢᵏ|φⱼᵏ⟩|²`). `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.335, 0.593, 0.753} / cross-group [0.000, 0.178] — 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 (single-bound-param **or** linear-combination angles), builds each concept statevector, and returns the analytic N×N overlap matrix; raises `MpsGramConfigurationError` (missing action, wrong signature, non-staircase effect, unrecognized angle expression, no call sites, or `bond_dim != 2`). ### Changed - Backwards-compatible: no behavior change for existing machines. Both halves of `extend-gate-set-and-parametric-actions` (multi-controlled gates, parametric actions) are additive. +- **Generalized angle parser** — `q_orca/angle.py::evaluate_angle` now accepts top-level linear combinations (`a + b`, `2*pi + gamma`, `-a - b`) in addition to the prior literal / single-identifier shapes. The Ry-segment matcher in `concept_gram_mps.py` parses the angle as a linear combination via `ast.parse` and substitutes call-site argument values per coefficient/parameter pair. The single-bound-param shape remains a degenerate one-term linear combination, so all archived examples parse unchanged. + +### Fixed + +- **MPS encoding factorization bug** (`fix-mps-encoding-non-factorizing`, [PR #48](../../pull/48)) — the original rung-1 staircase preparation `Ry(q0,α); CNOT(q0,q1); Ry(q1,β); CNOT(q1,q2); Ry(q2,γ)` was advertised as producing non-factorized concept overlap "by virtue of being entangled". This is mathematically false: while the staircase output has Schmidt rank > 1, its same-angle Gram matrix factorizes exactly as the product-state Gram (`|⟨φᵢ|φⱼ⟩|² = ∏ₖ cos²((θᵢᵏ − θⱼᵏ)/2)`), giving only 3 visible tiers, not 4. Replaced with the cross-coupled-by-sum encoding above, where the Ry on each non-leading qubit takes a linear combination of *two* bound parameters; this breaks the per-qubit factorization and yields the four-tier hierarchy the example claims. Adds new error kind `MpsGramConfigurationError("unrecognized_angle_expression")` for non-linear angle expressions in the Ry-segment matcher; appends post-mortem entry to `add-mps-concept-encoding/design.md`; cross-links new tech-debt §5.7 (verifier blind spot — Gram factorization vs. encoding entanglement) for a future verifier rule that would catch this class of bug at verify time. --- diff --git a/README.md b/README.md index 07b4fd4..006d62b 100644 --- a/README.md +++ b/README.md @@ -1087,6 +1087,7 @@ Each of these entered main with a full OpenSpec proposal → implementation → - ✅ **Completeness-check hardening** — measurement-bearing transitions are detected by action effect, not just event name *(`harden-completeness-detection`)* - ✅ **Bell-pair compiler fixture** — per-backend regression tests against the canonical two-qubit circuit *(`bell-pair-example`)* - ✅ **LARQL polysemantic clusters** — block-structured 12-concept polysemy on a 3-qubit register, matching the empirical signature reported by sparse-autoencoder studies of real transformer FFNs *(`add-polysemantic-clusters`)* +- ✅ **MPS concept encoding (rung 1)** — hierarchical 12-concept polysemy on a bond-2 matrix product state via a cross-coupled CNOT-staircase preparation `Ry(q0,a); CNOT(q0,q1); Ry(q1,a+b); CNOT(q1,q2); Ry(q2,b+c)`; produces a four-tier Gram matrix (self / sub-cluster-mate / super-group-sibling / cross-group). *(`add-mps-concept-encoding` + `fix-mps-encoding-non-factorizing`, [PR #46](../../pull/46) / [PR #48](../../pull/48))* See each folder under [`openspec/changes/archive/`](openspec/changes/archive/) for the proposal, design, and scenarios that shipped. @@ -1096,7 +1097,6 @@ Proposals with an OpenSpec folder and agreed scope, awaiting code. - 🧾 **Cross-machine composition** — `[invoke: Child(args) shots=N]` state-level delegation with static arg/return type-checking and a `## returns` section. Mermaid renders composed machines; QASM/Qiskit refuse until a runtime lands. *([`add-parameterized-invoke`](openspec/changes/add-parameterized-invoke/))* - 🧾 **Per-state runtime assertions** — `[assert: classical(qs[0]); entangled(qs[0], qs[1])]` annotations backed by statistical sampling on Stage 4b, with a `## assertion policy` section for shots/confidence/on-failure. *([`add-runtime-state-assertions`](openspec/changes/add-runtime-state-assertions/))* -- 🧾 **MPS concept encoding** — hierarchical polysemantic concept registers encoded as matrix-product states, extending the LARQL polysemantic family beyond the dense-statevector regime. *([`add-mps-concept-encoding`](openspec/changes/add-mps-concept-encoding/))* ### Longer-term — research / not yet specced diff --git a/docs/research/polysemantic-encoding-beyond-product-states.md b/docs/research/polysemantic-encoding-beyond-product-states.md index e1dec15..fd23877 100644 --- a/docs/research/polysemantic-encoding-beyond-product-states.md +++ b/docs/research/polysemantic-encoding-beyond-product-states.md @@ -371,9 +371,13 @@ confirmed in KB — see `mcp__q-orca-kb__batch_index`): - **`add-polysemantic-clusters`** (shipped, PR #31): the rung-0 example this note builds on. Status: merged to main. -- **`add-mps-concept-encoding`** (drafted alongside this note): the - rung-1 proposal. Adds example + demo + `compute_concept_gram_mps` - helper. No grammar changes. +- **`add-mps-concept-encoding`** (shipped, [PR #46](../../pull/46)) + + **`fix-mps-encoding-non-factorizing`** (shipped, [PR #48](../../pull/48), + merged 2026-05-01): the rung-1 proposal and its same-cycle correction + to the cross-coupled-by-sum encoding (per the caveat box above and + the post-mortem in `add-mps-concept-encoding/design.md`). Adds + example + demo + `compute_concept_gram_mps` helper supporting + linear-combination angles. No grammar changes. - **`concept-encoding-efficiency.md`** (companion research note): language-extension questions A/B/C. Question A becomes load-bearing for rung 2; this note does not require it. diff --git a/openspec/changes/add-mps-concept-encoding/design.md b/openspec/changes/archive/2026-05-02-add-mps-concept-encoding/design.md similarity index 100% rename from openspec/changes/add-mps-concept-encoding/design.md rename to openspec/changes/archive/2026-05-02-add-mps-concept-encoding/design.md diff --git a/openspec/changes/add-mps-concept-encoding/proposal.md b/openspec/changes/archive/2026-05-02-add-mps-concept-encoding/proposal.md similarity index 100% rename from openspec/changes/add-mps-concept-encoding/proposal.md rename to openspec/changes/archive/2026-05-02-add-mps-concept-encoding/proposal.md diff --git a/openspec/changes/add-mps-concept-encoding/specs/compiler/spec.md b/openspec/changes/archive/2026-05-02-add-mps-concept-encoding/specs/compiler/spec.md similarity index 100% rename from openspec/changes/add-mps-concept-encoding/specs/compiler/spec.md rename to openspec/changes/archive/2026-05-02-add-mps-concept-encoding/specs/compiler/spec.md diff --git a/openspec/changes/add-mps-concept-encoding/specs/language/spec.md b/openspec/changes/archive/2026-05-02-add-mps-concept-encoding/specs/language/spec.md similarity index 100% rename from openspec/changes/add-mps-concept-encoding/specs/language/spec.md rename to openspec/changes/archive/2026-05-02-add-mps-concept-encoding/specs/language/spec.md diff --git a/openspec/changes/add-mps-concept-encoding/tasks.md b/openspec/changes/archive/2026-05-02-add-mps-concept-encoding/tasks.md similarity index 100% rename from openspec/changes/add-mps-concept-encoding/tasks.md rename to openspec/changes/archive/2026-05-02-add-mps-concept-encoding/tasks.md diff --git a/openspec/changes/fix-mps-encoding-non-factorizing/design.md b/openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/design.md similarity index 100% rename from openspec/changes/fix-mps-encoding-non-factorizing/design.md rename to openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/design.md diff --git a/openspec/changes/fix-mps-encoding-non-factorizing/proposal.md b/openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/proposal.md similarity index 100% rename from openspec/changes/fix-mps-encoding-non-factorizing/proposal.md rename to openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/proposal.md diff --git a/openspec/changes/fix-mps-encoding-non-factorizing/specs/compiler/spec.md b/openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/specs/compiler/spec.md similarity index 100% rename from openspec/changes/fix-mps-encoding-non-factorizing/specs/compiler/spec.md rename to openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/specs/compiler/spec.md diff --git a/openspec/changes/fix-mps-encoding-non-factorizing/specs/language/spec.md b/openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/specs/language/spec.md similarity index 100% rename from openspec/changes/fix-mps-encoding-non-factorizing/specs/language/spec.md rename to openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/specs/language/spec.md diff --git a/openspec/changes/fix-mps-encoding-non-factorizing/tasks.md b/openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/tasks.md similarity index 100% rename from openspec/changes/fix-mps-encoding-non-factorizing/tasks.md rename to openspec/changes/archive/2026-05-02-fix-mps-encoding-non-factorizing/tasks.md diff --git a/openspec/changes/tech-debt-backlog/tasks.md b/openspec/changes/tech-debt-backlog/tasks.md index 7443c19..36f45be 100644 --- a/openspec/changes/tech-debt-backlog/tasks.md +++ b/openspec/changes/tech-debt-backlog/tasks.md @@ -609,6 +609,133 @@ remaining findings. immediate fix. (Source: 2026-05-01 `fix-mps-encoding-non-factorizing` post-mortem.) +The §5.8–§5.15 entries below come from a post-merge self-review of +PR #48 (`fix-mps-encoding-non-factorizing` implementation). They are +not example-library QA findings but the numbering continues §5 to +keep the 2026-05-01 cluster contiguous. + +- [ ] 5.8 **`evaluate_angle` regression on scientific-notation + literals.** Severity: HIGH. `q_orca/angle.py:127-164`. The new + top-level `+`/`-` splitter (`_split_linear_combination`) walks + character-by-character and treats any non-operator preceding char + as a license to split. The `e` in `1e-5` is non-operator, so the + splitter splits `1e-5` into `[1e, -5]` and `evaluate_angle("1e-5")` + raises `Unrecognized angle expression '1e'` where it returned + `1e-5` before this commit. Reproducer: + `evaluate_angle("1e-5*a", {"a": 0.1})` and + `evaluate_angle("1e-5 + a", ...)` both fail. Fix: in the look-back + at `angle.py:154`, also exclude `+`/`-` that follow `e`/`E` + preceded by a digit (treat as exponent sign). No example in the + repo uses scientific notation today, so this is latent but real; + add a regression test alongside the fix. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.9 **Inverse-form `Ry(qs[k], -(a + b))` does not parse end- + to-end.** Severity: HIGH. The language spec at + `openspec/changes/fix-mps-encoding-non-factorizing/specs/language/spec.md:64-66` + explicitly says `Ry(qs[k], -(a + b))` is "equivalently" valid + alongside `-a - b`. Two parser-side defects block it: + 1. `_ROTATION_GATE_ANGLE_RE` in + `q_orca/parser/markdown_parser.py:1170-1175` uses `[^)]+` to + capture the angle, which truncates `-(a+b)` to `-(a+b` at + the inner `)`. + 2. Even if captured, `_split_linear_combination` does not recurse + into parens, so `evaluate_angle("-(a + b)", ctx)` raises. + Reproducer: parsing an effect with `Ry(qs[2], -(b + c))` gives + parser error `unrecognized angle expression '-(b + c'`. The + helper's own AST walk handles `-(a+b)` fine, so the asymmetry is + purely on the parser. Fix: balance parens in the angle regex, and + have the splitter return `None` (delegate to a paren-stripping + retry) when the whole text is a parenthesized expression. The + shipped example sidesteps this by writing `-a - b`/`-b - c` — but + the spec promises both forms. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.10 **Test gap — bare-literal sad path uncovered.** + Severity: MEDIUM. The compiler spec scenario at + `openspec/changes/fix-mps-encoding-non-factorizing/specs/compiler/spec.md:90-101` + enumerates four trigger forms for `unrecognized_angle_expression`: + `a * b`, `sin(a)`, `a^2`, and **`Ry(qs[1], 2.5)`** (bare numeric + literal with no parameter reference). `tests/test_compiler.py` + (`TestComputeConceptGramMps`) covers `a * b` and `sin(a)` only — + bare-literal and `a^2` have no test. Manually verified that + `Ry(qs[1], 2.5)` does in fact raise with kind + `unrecognized_angle_expression`, but this is an untested behavior + the spec explicitly calls out. Fix: add + `test_unrecognized_angle_expression_bare_literal_raises` and + `test_unrecognized_angle_expression_power_raises` to + `TestComputeConceptGramMps`. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.11 **Test gap — inverse-form linear combination uncovered + by focused unit test.** Severity: MEDIUM. All happy-path tests + for cross-coupled angles in `tests/test_compiler.py` use the prep + form (`prepare_concept`, `Ry(qs[0], a); ...; Ry(qs[2], b + c)`). + No unit test exercises the inverse form (`Ry(qs[2], -b - c); ...; + Ry(qs[0], -a)`) with a linear-combination angle. The helper's + `is_inverse=True` branch with cross-coupled angles is only + covered transitively via + `test_examples.py::test_larql_polysemantic_hierarchical_pipeline`. + Fix: add a focused unit test in `TestComputeConceptGramMps` that + constructs an inverse-form effect with `-a - b` and asserts the + Gram matches the inverse of a prep-form Gram on the same angles. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.12 **`_parse_linear_combination` rejects `2*-a`.** + Severity: LOW. `q_orca/compiler/concept_gram_mps.py:147-165` only + matches `Constant * Name` and `Name * Constant` for `Mult`; the + AST shape `Constant * UnaryOp(USub, Name)` (i.e., `2*-a`) is + rejected as "product of two non-constant terms". This is a + syntactically valid linear combination. Unlikely in practice — + users would write `-2*a` — but the matching is incomplete. Fix: + when one operand is `Constant` and the other is a `UnaryOp` over + a `Name`, multiply the constant into the sign and recurse, or + permit `walk(other_operand, sign * float(const))`. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.13 **Misleading `_parse_linear_combination` error message + for all-constant products.** Severity: LOW. Same site as §5.12: + `2*3` is rejected with `"product of two non-constant terms"` even + though both terms *are* constants. The intent is to reject + expressions with no parameter reference, and a separate branch + already handles bare `Constant`. Fix: tighten the `Mult` fall- + through message to say "no parameter reference" when both sides + are constants, or short-circuit the case earlier with a clearer + diagnostic. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.14 **Polysemy column tabulates `0.000` for entries that + compute as ~`1e-4`.** Severity: LOW. + `examples/larql-polysemantic-hierarchical.q.orca.md:138-139` + claims `mango (6) → 0.000` and `papaya (7) → 0.000`. Actual + computed values are `~0.00012` (rounding to `0.000` at 3-decimal + display; ASCII heatmap correctly shows blank). Not numerically + wrong, but a reader running `compute_concept_gram_mps` and seeing + `9.5e-5` may read the rounded display as misleadingly "exactly + zero". Fix optional — add a "≈" prefix on near-zero rows or + document the rounding convention in the surrounding paragraph. + (Source: 2026-05-01 PR #48 self-review.) + +- [ ] 5.15 **Documentation/contract polish on + `MpsGramConfigurationError` and the compiler spec example.** + Severity: NIT. Two minor doc-vs-code mismatches: + - `q_orca/compiler/concept_gram_mps.py:66-78`: the + `MpsGramConfigurationError` class docstring lists only + `unrecognized_angle_expression` as a possible `kind`. All + other configuration errors in the module raise without a + `kind` (correct), but the docstring could note that + explicitly so callers know to fall through to message + inspection for non-angle errors. + - `openspec/changes/fix-mps-encoding-non-factorizing/specs/compiler/spec.md:107-109` + says "the second and third `qc.ry(` calls receiving the + *evaluated* linear combination (e.g., `qc.ry(a_value + + b_value, 1)` rather than a single bound parameter)". The + actual Qiskit compiler emits a *fully-evaluated* float + (e.g., `qc.ry(-1.594, 1)`), not the symbolic + `a_value + b_value` form. Both satisfy the contract, but + the spec example is misleading. + (Source: 2026-05-01 PR #48 self-review.) + ## 6. How to use this file - [x] 6.1 **Meta**: when an item is fixed, leave the task checked diff --git a/openspec/specs/compiler/spec.md b/openspec/specs/compiler/spec.md index 00c2eca..028d639 100644 --- a/openspec/specs/compiler/spec.md +++ b/openspec/specs/compiler/spec.md @@ -640,3 +640,149 @@ the comparison operator, the bound, and a pass/fail marker. - **THEN** the `t_count` row contains the measured value and no bound or pass/fail marker +### Requirement: MPS Concept Gram Matrix Analysis Helper + +The compiler package SHALL expose an optional analysis helper +`compute_concept_gram_mps(machine, concept_action_label: str = +"query_concept", bond_dim: int = 2) -> numpy.ndarray[complex]` +that returns the `N × N` concept-overlap matrix for machines +following the MPS (matrix product state) concept-preparation +convention. + +The helper SHALL assume the following convention is in effect: + +1. The named parametric action has signature + `(qs, ) -> qs` where `n` matches the size of + the `qubits` register declared in `## context`. The number of + angle parameters is NOT fixed at three — it scales with the + register size. + +2. The action's effect is a CNOT-staircase of the form + `Ry(qs[0], ); CNOT(qs[0], qs[1]); Ry(qs[1], ); + CNOT(qs[1], qs[2]); ... Ry(qs[n-1], )` — exactly `n` + single-qubit `Ry` rotations and `n-1` CNOTs between adjacent + qubits, in staircase order. Each `` SHALL be a *linear + combination of the action's bound angle parameters* — i.e., a + sum of terms each of the form `c · p` where `c` is a numeric + coefficient (defaulting to 1 when omitted) and `p` is one of the + action's angle parameter names. A single-parameter expression + like `Ry(qs[0], a)` is the degenerate case `1 · a` and SHALL be + accepted. The inverse pattern (for query actions: reversed gate + order, negated angle expressions, CNOTs self-inverse) is also + accepted. + +3. The machine's transitions table contains `N ≥ 1` call sites to + this action, each with a literal angle tuple. + +4. The `bond_dim` parameter is currently fixed at `2`. Values other + than `2` SHALL raise `MpsGramConfigurationError` with a message + indicating that higher bond dimensions are not yet implemented. + +Given this convention, `compute_concept_gram_mps` SHALL enumerate +the call sites in transition-declaration order, build the MPS state +`|c_i⟩` per call by evaluating the staircase circuit on `|0^n⟩` +(substituting the bound argument values into each `Ry`'s linear- +combination angle expression to produce a float angle), and return +the matrix with `gram[i, j] = ⟨c_i | c_j⟩` (complex-valued inner +product; values are real for the canonical `Ry` + CNOT staircase +encoding). + +The helper is an analysis utility and SHALL NOT be part of the +main compile / verify / simulate pipeline. It has no effect on any +compiler entry point other than being importable from the +`q_orca.compiler.concept_gram_mps` module (and re-exported from the +top-level `q_orca` package). + +The helper coexists with `compute_concept_gram` (the product-state +helper from `add-polysemantic-clusters`) — the two are separate +entry points and the caller picks based on which preparation +convention their example uses. Automatic ansatz detection is out +of scope. + +#### Scenario: Happy path on polysemantic-hierarchical example with cross-coupled angles + +- **GIVEN** the parsed machine from + `examples/larql-polysemantic-hierarchical.q.orca.md`, which has + 12 call sites to a `query_concept` action with a cross-coupled- + by-sum staircase effect (e.g., `Ry(qs[0], a); CNOT(qs[0], qs[1]); + Ry(qs[1], a + b); CNOT(qs[1], qs[2]); Ry(qs[2], b + c)`) +- **WHEN** `compute_concept_gram_mps(machine)` is invoked (default + label `"query_concept"`, default `bond_dim = 2`) +- **THEN** the return value is a `(12, 12)` NumPy complex array +- **AND** `|gram[i, i]| == 1` for all diagonal entries +- **AND** the off-diagonal `|gram[i, j]|²` entries partition into + exactly four tiers as documented in the example's leading + paragraph, within a tolerance of `1e-6` per entry +- **AND** the helper successfully evaluates each Ry's linear- + combination angle expression by substituting the call site's + bound argument values + +#### Scenario: Single-parameter Ry continues to parse (degenerate linear combination) + +- **GIVEN** a machine with a strict single-bound-param staircase + effect `Ry(qs[0], a); CNOT(qs[0], qs[1]); Ry(qs[1], b); CNOT(qs[1], + qs[2]); Ry(qs[2], c)` +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper parses each Ry's angle as a 1-term linear + combination (`1 · a`, `1 · b`, `1 · c`) and returns the same Gram + matrix it produced before this change + +#### Scenario: Non-linear angle expression raises structured error + +- **GIVEN** a machine where one Ry's angle expression is non-linear + in the bound parameters — e.g., `Ry(qs[1], a * b)`, + `Ry(qs[1], sin(a))`, `Ry(qs[1], a^2)`, or `Ry(qs[1], 2.5)` (a + bare numeric literal with no parameter reference) +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper raises `MpsGramConfigurationError` with kind + `unrecognized_angle_expression` whose message names the offending + expression, the action, the machine, and lists the supported + shape (linear combination of bound angle parameters with optional + numeric coefficients) + +#### Scenario: Wrong signature shape raises structured error + +- **GIVEN** a machine where the parametric action named + `query_concept` has signature `(qs, c: int) -> qs` (single int + parameter, not n angle parameters) +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper raises `MpsGramConfigurationError` whose + message names the action, the machine, and the required + signature shape (n angle parameters matching register size) + +#### Scenario: Non-staircase effect raises structured error + +- **GIVEN** a machine where `query_concept` has the right signature + shape but an effect that is not a CNOT-staircase (e.g., product- + state only — no CNOTs — or CNOTs between non-adjacent qubits) +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper raises `MpsGramConfigurationError` whose + message identifies the unexpected gate pattern and names the + required staircase shape + +#### Scenario: Missing action raises structured error + +- **GIVEN** a machine with no parametric action named + `query_concept` +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper raises `MpsGramConfigurationError` whose + message names the missing action and the machine, and lists the + available parametric actions as a hint + +#### Scenario: No call sites raises structured error + +- **GIVEN** a machine with a `query_concept` action of the right + shape but zero transitions that invoke it +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the helper raises `MpsGramConfigurationError` noting + that `query_concept` has no call sites in the transitions table + +#### Scenario: Unsupported bond dimension raises structured error + +- **GIVEN** the canonical example +- **WHEN** `compute_concept_gram_mps(machine, bond_dim=4)` is + invoked +- **THEN** the helper raises `MpsGramConfigurationError` with a + message indicating that only `bond_dim=2` is currently + implemented + diff --git a/openspec/specs/language/spec.md b/openspec/specs/language/spec.md index b7876e2..852a052 100644 --- a/openspec/specs/language/spec.md +++ b/openspec/specs/language/spec.md @@ -571,3 +571,148 @@ metric set: all five recognized names. - **THEN** `machine.resource_metrics == []` and parsing succeeds with no warnings +### Requirement: Hierarchical Polysemantic Example Pattern + +The example library SHALL include at least one *hierarchical- +overlap* polysemantic machine that demonstrates a concept geometry +whose pairwise overlap matrix is **non-factorized** — that is, +`⟨c_i | c_j⟩` does NOT decompose as a product over per-qubit +cosines of single-angle differences. The canonical file is +`examples/larql-polysemantic-hierarchical.q.orca.md`. + +A hierarchical-polysemantic example SHALL satisfy these invariants: + +1. **Compact concept register.** The `## context` declares a + fixed-size `qubits: list` with `n` qubits where `2^n ≥ N` + and `N` is the number of concepts. The canonical example uses + `n = 3, N = 12`. + +2. **Bond-2 MPS concept encoding with non-factorized Gram.** Each + concept `c_i` is prepared from `|0^n⟩` by a CNOT-staircase + circuit consisting of `n` single-qubit `Ry` rotations interleaved + with `n-1` CNOTs between adjacent qubits. The angle bound to + each `Ry` MAY be a single parameter or a *linear combination* of + the action's angle parameters (e.g., `α + β`). At least one of + the `Ry` rotations SHALL bind a multi-term linear combination so + that the Gram matrix does not factorize. The canonical example + uses the cross-coupled-by-sum encoding + + Ry(qs[0], a) + ; CNOT(qs[0], qs[1]) + ; Ry(qs[1], a + b) + ; CNOT(qs[1], qs[2]) + ; Ry(qs[2], b + c) + + Higher-bond-dim variants MAY add further 2-qubit gates per + staircase step; this requirement addresses only the bond-dim-2 + canonical shape. + +3. **Non-factorization criterion.** The encoding's Gram matrix + SHALL differ measurably from the same-angle product-state Gram. + Specifically: with `gram_prod[i, j] = ∏_k cos((θ_{i,k} − + θ_{j,k})/2)` over the action's `n` angle parameters, the canonical + example SHALL satisfy `max_{i ≠ j} | |gram[i,j]|² − + |gram_prod[i,j]|² | ≥ 0.05`. The strict-staircase shape + `Ry(qs[k], )` interleaved with CNOTs — used by + `add-mps-concept-encoding` and shown to factorize in this + change's design.md — does NOT satisfy the non-factorization + criterion and is NOT a permitted shape for the canonical + hierarchical example. (It remains a permitted shape for *future* + examples that document the factorization explicitly as a + teaching point.) + +4. **Single parametric preparation action.** Exactly one parametric + action with signature `(qs, ) -> qs` and a + matching CNOT-staircase effect satisfying invariants 2 and 3. + The N concepts are 1-to-1 with the N parametric call sites to + this action, not with N copy-pasted actions. + +5. **Single parametric query action.** Exactly one parametric + action with the same angle-typed signature as the prepare action + and an effect that is the exact inverse of the prepare effect + (gate order reversed, angle-expression signs negated, CNOTs + self-inverse so they reappear in reversed position). When the + prepare effect binds a linear combination like `Ry(qs[k], a + b)`, + the inverse is `Ry(qs[k], -(a + b))` (equivalently `Ry(qs[k], -a + - b)`). + +6. **Documented hierarchical Gram matrix.** The example's leading + paragraph SHALL tabulate the analytic `|⟨c_i | c_j⟩|²` matrix + and SHALL call out at least **four tiers**: self (1.0), sub- + cluster-mate, super-group-sibling, and cross-group. Flat-tier + examples like `larql-polysemantic-clusters.q.orca.md` (three + tiers) do NOT satisfy this invariant and are categorized + separately. + +7. **Documented polysemy column for a loaded concept.** The example + SHALL identify a specific concept `c_0` and tabulate the + analytic `P(|0^n⟩ | query_i)` values when the feature state is + `|f⟩ = |c_0⟩`. The tabulated values SHALL exhibit the same four- + tier structure as row 0 of the Gram matrix. + +The existing `larql-polysemantic-2`, `larql-polysemantic-12`, and +`larql-polysemantic-clusters` examples remain valid and unchanged; +they demonstrate the parametric-action mechanism with progressively +richer product-state geometry. The +`larql-polysemantic-hierarchical.q.orca.md` example demonstrates +the first non-product-state, non-factorized-Gram encoding in the +polysemantic example family. + +#### Scenario: Canonical example parses and verifies + +- **WHEN** `parse_q_orca_markdown(open( + "examples/larql-polysemantic-hierarchical.q.orca.md").read())` is + invoked +- **THEN** `parsed.errors == []` +- **AND** `verify(parsed.file.machines[0]).valid == True` + +#### Scenario: Canonical example compiles to expected register size + +- **GIVEN** the canonical example has `n = 3, N = 12` +- **WHEN** `compile_to_qasm(machine)` and `compile_to_qiskit(machine)` + are invoked +- **THEN** the QASM output contains `qubit[3] q;` +- **AND** the Qiskit script contains `QuantumCircuit(3)` +- **AND** the Qiskit script contains both `qc.ry(` calls and + `qc.cx(` calls in the expected staircase pattern, with the + second and third `qc.ry(` calls receiving the *evaluated* linear + combination (e.g., `qc.ry(a_value + b_value, 1)`) rather than a + single bound parameter +- **AND** the total number of `qc.ry(` calls matches the per- + call-site expansion (3 for prepare + 3 × 12 = 36 for queries, + total 39) +- **AND** the total number of `qc.cx(` calls matches the staircase + CNOT expansion (2 for prepare + 2 × 12 = 24 for queries, + total 26) + +#### Scenario: Four-tier structure is checkable via compute_concept_gram_mps + +- **GIVEN** the canonical example +- **WHEN** `compute_concept_gram_mps(machine)` is invoked +- **THEN** the returned matrix's `|gram[i,j]|²` values partition + into exactly four tiers as documented in the example's leading + paragraph, within a numerical tolerance of `1e-6` on each entry + +#### Scenario: Encoding's Gram differs measurably from same-angle product-state Gram + +- **GIVEN** the canonical example and its 12 angle triples `(a_i, + b_i, c_i)` +- **WHEN** `gram_mps = compute_concept_gram_mps(machine)` and + `gram_prod[i, j] = cos((a_i − a_j)/2) · cos((b_i − b_j)/2) · + cos((c_i − c_j)/2)` are computed +- **THEN** `max_{i ≠ j} | |gram_mps[i,j]|² − |gram_prod[i,j]|² | + ≥ 0.05` + +#### Scenario: Strict-staircase factorizing shape is rejected as canonical + +- **GIVEN** a candidate hierarchical example whose prepare effect + is the strict-staircase shape `Ry(qs[0], a); CNOT(qs[0], qs[1]); + Ry(qs[1], b); CNOT(qs[1], qs[2]); Ry(qs[2], c)` (single-bound- + param Ry rotations with no linear combinations) +- **WHEN** the encoding's Gram is compared against the same-angle + product-state Gram +- **THEN** the two Grams are equal to within machine epsilon (the + staircase factorizes), violating invariant 3 of this requirement +- **AND** the example does NOT satisfy this requirement as the + canonical hierarchical example + diff --git a/q_orca/__init__.py b/q_orca/__init__.py index c5f9b46..4e6b29c 100644 --- a/q_orca/__init__.py +++ b/q_orca/__init__.py @@ -28,7 +28,7 @@ from q_orca.config import load_config, QOrcaConfig from q_orca.llm import create_provider, LLMProvider -__version__ = "0.6.0" +__version__ = "0.7.0" __all__ = [ # Parser