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 @@ -7,6 +7,7 @@
- **Parametric actions** — action signatures now accept typed positional parameters (`int` for `qs[...]` subscripts and `angle` for rotation-gate angles) after the leading qubit-list parameter. Call sites in the transitions table supply literal arguments; the compiler substitutes them into a fresh copy of the effect string per site. One `query_concept | (qs, c: int) -> qs | Hadamard(qs[c])` row replaces N copy-pasted actions. Parameters are compile-time constants — out-of-range subscripts and unbound identifiers raise structured parse-time errors that name the offending transition. Zero-parameter signatures (`(qs) -> qs`, `(ctx) -> ctx`) parse unchanged, so every existing example is additive-compatible.
- **New example `examples/larql-polysemantic-12.q.orca.md`** — 12-qubit concept register, 12 non-orthogonal concept vectors (pairwise overlap 1/2), one parametric `query_concept(c: int)` action stamping 12 call sites from one template. Documents the analytic polysemy / cross-talk table in the leading paragraph.
- **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.

### Changed
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Every machine passes through 5 stages in order. A failure in stage 1 stops the p
| 3 — Determinism | `determinism.py` | Guards on competing transitions are mutually exclusive |
| 4 — Quantum | `quantum.py` | Unitarity of gates, no-cloning violations, entanglement declarations, collapse probability sum = 1 |
| 4b — Dynamic | `dynamic.py` | QuTiP circuit simulation: actual Schmidt rank and Von Neumann entropy for every declared entangled state |
| 4c — Resource bounds | `resources.py` | `gate_count`, `depth`, `cx_count`, `t_count`, `logical_qubits` against budgets declared in `## invariants` (skipped when no resource invariants are present) |
| 5 — Superposition | `superposition.py` | No superposition coherence leaks across unguarded transitions |

Stage 4b is a soft dependency: if QuTiP is not installed it skips gracefully and CI still passes.
Expand Down Expand Up @@ -265,6 +266,7 @@ q-orca verify examples/bell-entangler.q.orca.md --skip-dynamic
| `--skip-completeness` | Skip stage 2: event completeness checks |
| `--skip-quantum` | Skip stage 4: unitarity, no-cloning, entanglement |
| `--skip-dynamic` | Skip stage 4b: QuTiP circuit simulation |
| `--skip-resource-bounds` | Skip stage 4c: resource invariant checks |
| `--backend BACKEND` | Verification backend: `qutip` (default), `cuquantum`, `cudaq` |
| `--gpu-count N` | Number of GPUs to use (cuquantum backend) |
| `--tensor-network` | Use tensor-network contraction (cuquantum backend) |
Expand Down Expand Up @@ -618,6 +620,7 @@ The 5 verification stages are:
| 2 Completeness | `completeness.py` | Every (state, event) pair has a transition |
| 3 Determinism | `determinism.py` | Guards are mutually exclusive |
| 4 Quantum | `quantum.py` + `dynamic.py` | Unitarity, no-cloning, entanglement (QuTiP), collapse completeness |
| 4c Resource bounds | `resources.py` | Static gate/depth/CX/T/qubit budgets vs `## invariants` |
| 5 Superposition | `superposition.py` | No superposition coherence leaks |

---
Expand Down
114 changes: 114 additions & 0 deletions docs/language/resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Resource estimation

Q-Orca compiles each `.q.orca.md` machine to a Qiskit `QuantumCircuit`,
then asks the Qiskit transpiler for five static cost numbers. You can
declare which numbers a machine cares about with a `## resources`
table, and you can pin static budgets via the existing `## invariants`
grammar. The verifier turns budget violations into errors at verify
time, before any hardware run.

## The five metrics

| Metric | Definition |
|-------------------|------------|
| `gate_count` | Total ops on the **un-transpiled** circuit (the literal gate sequence the machine declares). |
| `depth` | Critical-path depth after `transpile(qc, optimization_level=1)`. |
| `cx_count` | Number of `cx` ops after `transpile(qc, basis_gates=['u3', 'cx'], optimization_level=1)` — NISQ-relevant. |
| `t_count` | `t + tdg` count after `transpile(qc, basis_gates=['h', 's', 'cx', 't', 'tdg'], optimization_level=1)` — Clifford+T fault-tolerance proxy. |
| `logical_qubits` | Declared qubit count from `## context` (`qubits: list<qubit>` or fallback rules in the compiler spec). |

`gate_count` is intentionally pre-transpile: it tells you what the
machine *says*. The other four are post-transpile against canonical
basis sets so they compare cleanly across machines.

## `## resources` section

Optional. Declares which metrics the compiler should report alongside
the script.

```markdown
## resources
| Metric | Basis |
|----------------|------------|
| gate_count | logical |
| depth | logical |
| cx_count | u3+cx |
```

The `Basis` column is documentation only. A third `Notes` column is
also accepted. Unknown metric names produce a structured
`unknown_resource_metric` error during parsing.

If the section is omitted, `compile_with_resources` reports all five
metrics by default.

## Resource invariants

The `## invariants` block already accepts entanglement and Schmidt-rank
claims. It additionally accepts the five resource identifiers as the
LHS of a comparison against an integer literal:

```markdown
## invariants
- gate_count <= 9
- depth <= 5
- cx_count <= 6
- t_count == 0
- logical_qubits == 3
```

Operators: `<=`, `<`, `==`, `>=`, `>`. RHS must be an integer literal.

## Verification rule `resource_bounds`

The verifier runs `check_resource_invariants` automatically when at
least one resource invariant is present in `## invariants`. To opt out
explicitly, omit the invariants or set
`VerifyOptions(skip_resource_bounds=True)`.

Diagnostics:

- `RESOURCE_BOUND_EXCEEDED` (error) — measured value violates the
declared bound. Message names the metric, the measured value, the
operator, and the bound.
- `RESOURCE_BOUND_INDETERMINATE` (warning) — the metric came back
`"unknown"` because of a runtime-bound construct (currently no
shipped feature triggers this; the branch exists for forward
compatibility with parameterized loop counts).
- `unknown_resource_metric` (parser error) — `## resources` row names
a metric outside the recognized five.

## Programmatic API

```python
from q_orca import (
parse_q_orca_markdown,
estimate_resources,
compile_with_resources,
format_resource_report,
)

machine = parse_q_orca_markdown(open("my.q.orca.md").read()).file.machines[0]

# Just the numbers
resources = estimate_resources(machine)
# → {'gate_count': 2, 'depth': 2, 'cx_count': 1, 't_count': 0, 'logical_qubits': 2}

# Full one-shot: Qiskit script + numbers
script, resources = compile_with_resources(machine)

# One-screen summary table
print(format_resource_report(machine, resources))
# gate_count : 2 <= 2 ✓
# cx_count : 1 == 1 ✓
```

`estimate_resources` is memoized by `id(machine)`, so the verifier
and compiler share one transpile pass per call site.

## Default-metric-set fallback

If a machine has no `## resources` section, `compile_with_resources`
and `format_resource_report` use the canonical default order:
`gate_count, depth, cx_count, t_count, logical_qubits`. A machine with
a `## resources` section uses that section's order verbatim.
11 changes: 11 additions & 0 deletions examples/bell-entangler.q.orca.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@
- entanglement: final state must have Schmidt rank >1 before measure
- completeness: all possible collapses covered (no missing branches)
- no-cloning: no copy ops allowed
- resource_bounds: pinned gate budget for the prep circuit

## resources
| Metric | Basis |
|------------|---------|
| gate_count | logical |
| cx_count | logical |

## invariants
- gate_count <= 2
- cx_count == 1
15 changes: 15 additions & 0 deletions examples/qaoa-maxcut.q.orca.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,18 @@

## verification rules
- unitarity: all gates preserve norm
- resource_bounds: NISQ-fit budget for the per-layer prep circuit

## resources
| Metric | Basis |
|----------------|------------|
| gate_count | logical |
| depth | logical |
| cx_count | u3+cx |
| logical_qubits | - |

## invariants
- gate_count <= 9
- depth <= 5
- cx_count <= 6
- logical_qubits == 3
15 changes: 15 additions & 0 deletions examples/vqe-heisenberg.q.orca.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,18 @@
- unitarity: all gates preserve norm
- completeness: all branches converge or continue iterating
- no-cloning: no copy operations
- resource_bounds: ansatz cost budget for early-NISQ hardware

## resources
| Metric | Basis |
|----------------|------------|
| gate_count | logical |
| depth | logical |
| cx_count | u3+cx |
| logical_qubits | - |

## invariants
- gate_count <= 3
- depth <= 1
- cx_count <= 1
- logical_qubits == 2
66 changes: 33 additions & 33 deletions openspec/changes/add-resource-estimation/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@

## 1. AST extensions

- [ ] 1.1 Add `QMachine.resource_metrics: list[str] = field(default_factory=list)`
- [x] 1.1 Add `QMachine.resource_metrics: list[str] = field(default_factory=list)`
to `q_orca/ast.py`. Empty list means "use the default metric
set" downstream.
- [ ] 1.2 Extend the `Invariant` dataclass with a new `kind` value
- [x] 1.2 Extend the `Invariant` dataclass with a new `kind` value
`"resource"` and a `metric: str | None = None` field. Existing
`entanglement` and `schmidt_rank` invariants continue to set
`metric=None`.

## 2. Parser — `## resources` section

- [ ] 2.1 Add `_parse_resources_table(table, errors) -> list[str]` in
- [x] 2.1 Add `_parse_resources_table(table, errors) -> list[str]` in
`q_orca/parser/markdown_parser.py`. Accept a 2- or 3-column
table with header `Metric | Basis | Notes?`. Return the list of
metric names from the first column.
- [ ] 2.2 Validate each metric name against the recognized set
- [x] 2.2 Validate each metric name against the recognized set
(`gate_count`, `depth`, `cx_count`, `t_count`,
`logical_qubits`). Unknown names SHALL append a structured
`unknown_resource_metric` error referencing the row.
- [ ] 2.3 Wire `_parse_machine_chunk` to detect `## resources` after
- [x] 2.3 Wire `_parse_machine_chunk` to detect `## resources` after
the existing `## actions` and `## invariants` parsing. Section
is optional; absence leaves `resource_metrics=[]`.

## 3. Parser — resource invariants

- [ ] 3.1 Extend the `## invariants` bullet-list grammar in
- [x] 3.1 Extend the `## invariants` bullet-list grammar in
`q_orca/parser/markdown_parser.py` to recognize five new
identifiers (`gate_count`, `depth`, `cx_count`, `t_count`,
`logical_qubits`) on the LHS of a comparison.
- [ ] 3.2 Accept the comparison operators `<=`, `<`, `==`, `>=`, `>`
- [x] 3.2 Accept the comparison operators `<=`, `<`, `==`, `>=`, `>`
and an integer literal RHS. Produce
`Invariant(kind="resource", metric=<name>, op=<op>,
value=<int>)`.
- [ ] 3.3 Add tests in `tests/test_parser.py::TestResourceInvariants`
- [x] 3.3 Add tests in `tests/test_parser.py::TestResourceInvariants`
covering each metric × operator combination plus an
unknown-identifier error case.

## 4. Compiler — `estimate_resources`

- [ ] 4.1 Create `q_orca/compiler/resources.py` with
- [x] 4.1 Create `q_orca/compiler/resources.py` with
`estimate_resources(machine) -> dict[str, int | str]`.
- [ ] 4.2 Build the Qiskit circuit by reusing the existing
- [x] 4.2 Build the Qiskit circuit by reusing the existing
circuit-construction helpers in `q_orca/compiler/qiskit.py`.
Do not duplicate gate emission.
- [ ] 4.3 Compute each metric:
- [x] 4.3 Compute each metric:
- `gate_count` — sum gate effects from the un-transpiled
circuit.
- `depth` — `transpile(qc, optimization_level=1).depth()`.
Expand All @@ -54,46 +54,46 @@
- `t_count` — `transpile(qc, basis_gates=['h','s','cx','t','tdg'],
optimization_level=1).count_ops()`, summing `t` + `tdg`.
- `logical_qubits` — `len(machine.context['qubits'])`.
- [ ] 4.4 Memoize the result by `id(machine)` so repeated calls
- [x] 4.4 Memoize the result by `id(machine)` so repeated calls
within one verify-or-compile invocation are free.
- [ ] 4.5 Return `"unknown"` for any metric whose computation
- [x] 4.5 Return `"unknown"` for any metric whose computation
fails because of a runtime-bound `[loop N]`. Today no shipped
feature triggers this; the branch exists for forward
compatibility.

## 5. Compiler — `compile_with_resources`

- [ ] 5.1 Add `compile_with_resources(machine, options) -> tuple[str,
- [x] 5.1 Add `compile_with_resources(machine, options) -> tuple[str,
dict]` to `q_orca/compiler/qiskit.py` (or a new top-level entry
in `q_orca/__init__.py`). Returns the Qiskit script and the
resource dict in one call.
- [ ] 5.2 Format the resource report as a one-screen summary table:
- [x] 5.2 Format the resource report as a one-screen summary table:
`metric : value [≤ bound] [✓|✗]`. Bound and pass/fail are
omitted when the machine has no invariant for that metric.
- [ ] 5.3 Export `estimate_resources` and `compile_with_resources`
- [x] 5.3 Export `estimate_resources` and `compile_with_resources`
from `q_orca/__init__.py`.

## 6. Verifier — `check_resource_invariants`

- [ ] 6.1 Add `check_resource_invariants(machine) -> list[VerifyError]`
- [x] 6.1 Add `check_resource_invariants(machine) -> list[VerifyError]`
to `q_orca/verifier/dynamic.py`. For each
`Invariant(kind="resource")`, evaluate the metric via
`estimate_resources(machine)` and apply the comparison.
- [ ] 6.2 On violation, emit a `VerifyError` with code
- [x] 6.2 On violation, emit a `VerifyError` with code
`RESOURCE_BOUND_EXCEEDED`, message naming the metric, the
measured value, the operator, and the bound.
- [ ] 6.3 On indeterminate measurement (`"unknown"` returned), emit
- [x] 6.3 On indeterminate measurement (`"unknown"` returned), emit
a `VerifyError` with severity `warning` and code
`RESOURCE_BOUND_INDETERMINATE`.
- [ ] 6.4 Activate the rule under the name `resource_bounds` in
- [x] 6.4 Activate the rule under the name `resource_bounds` in
`## verification rules`. Default state: enabled when any
resource invariant is present, else skipped (zero cost).
- [ ] 6.5 Wire the rule into the verifier's main dispatch alongside
- [x] 6.5 Wire the rule into the verifier's main dispatch alongside
`check_unitarity`, `check_completeness`, etc.

## 7. Tests

- [ ] 7.1 `tests/test_resource_estimation.py` (new file):
- [x] 7.1 `tests/test_resource_estimation.py` (new file):
- `test_bell_pair_resources`: `bell-entangler.q.orca.md`
produces `gate_count=2, depth=2, cx_count=1, t_count=0,
logical_qubits=2`.
Expand All @@ -110,7 +110,7 @@
- `test_no_resources_section_uses_default_metrics`:
`compile_with_resources` on a machine without `##
resources` returns all five metrics.
- [ ] 7.2 `tests/test_verifier.py`:
- [x] 7.2 `tests/test_verifier.py`:
- `test_resource_bound_exceeded`: a machine with
`cx_count <= 0` and a CNOT in its action emits
`RESOURCE_BOUND_EXCEEDED`.
Expand All @@ -119,45 +119,45 @@
- `test_resource_invariants_skipped_when_absent`: a machine
with no resource invariants does not invoke
`estimate_resources` (assert via patching).
- [ ] 7.3 `tests/test_parser.py::TestResourcesSection`:
- [x] 7.3 `tests/test_parser.py::TestResourcesSection`:
- 2-column form parses.
- 3-column form with `Notes` parses identically.
- Unknown metric name produces structured error.
- Missing section leaves `resource_metrics=[]`.

## 8. Examples

- [ ] 8.1 Update `examples/qaoa-maxcut.q.orca.md` to include a
- [x] 8.1 Update `examples/qaoa-maxcut.q.orca.md` to include a
`## resources` section listing all five metrics, plus a
`## invariants` block with pinned bounds. Run the example
through the verifier; the bounds SHALL pass at the values the
example currently produces.
- [ ] 8.2 Update `examples/vqe-heisenberg.q.orca.md` similarly.
- [ ] 8.3 Update `examples/bell-entangler.q.orca.md` to demonstrate
- [x] 8.2 Update `examples/vqe-heisenberg.q.orca.md` similarly.
- [x] 8.3 Update `examples/bell-entangler.q.orca.md` to demonstrate
a minimal resource section: `gate_count <= 2; cx_count == 1`.
Smallest possible illustration.

## 9. Documentation

- [ ] 9.1 New file `docs/language/resources.md` covering:
- [x] 9.1 New file `docs/language/resources.md` covering:
- Surface syntax of `## resources` (with example).
- Metric definitions (un-optimized vs post-transpile).
- Resource invariants in `## invariants`.
- Diagnostic codes (`RESOURCE_BOUND_EXCEEDED`,
`RESOURCE_BOUND_INDETERMINATE`,
`unknown_resource_metric`).
- The default-metric-set fallback rule.
- [ ] 9.2 Update README's verifier rule list with `resource_bounds`.
- [ ] 9.3 CHANGELOG entry under the next release noting the
- [x] 9.2 Update README's verifier rule list with `resource_bounds`.
- [x] 9.3 CHANGELOG entry under the next release noting the
additive `## resources` section, the five new invariant
identifiers, and the two new compiler entry points.

## 10. Spec consistency

- [ ] 10.1 `openspec validate add-resource-estimation --strict` is
- [x] 10.1 `openspec validate add-resource-estimation --strict` is
green.
- [ ] 10.2 Full pytest suite green.
- [ ] 10.3 Ruff clean across the touched files.
- [x] 10.2 Full pytest suite green.
- [x] 10.3 Ruff clean across the touched files.
- [ ] 10.4 Run `openspec archive add-resource-estimation` after
merge so the deltas land in
`openspec/specs/{language,compiler,verifier}/spec.md`.
Loading
Loading