Skip to content
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ pip install q-orca[all] # + MCP server (pyyaml)
pip install q-orca # CLI + verifier only, no quantum libs
```

### Verify the installation

```bash
q-orca verify examples/bell-entangler.q.orca.md --strict
```

Expected output:
```
Machine: BellEntangler
Result: VALID
```

### Optional backends

```bash
# CUDA-Q (macOS Apple Silicon, Linux, Windows — CPU simulation, no GPU required)
# Tested: cudaq==0.14.0
pip install cudaq matplotlib
q-orca verify examples/bell-entangler.q.orca.md --backend cudaq

# cuQuantum (Linux + NVIDIA GPU only)
pip install qutip-cuquantum
q-orca verify examples/bell-entangler.q.orca.md --backend cuquantum
```

If a backend package is not installed, Q-Orca falls back to QuTiP automatically.

---

## Setup (development)
Expand Down Expand Up @@ -116,6 +143,18 @@ Every machine passes through 5 stages in order. A failure in stage 1 stops the p

Stage 4b is a soft dependency: if QuTiP is not installed it skips gracefully and CI still passes.

### Verification Backends

Stage 4b supports three backends via `--backend`:

| Backend | Flag | Requires | Platform |
|---------|------|----------|----------|
| QuTiP (default) | `--backend qutip` | `pip install qutip` | Any |
| NVIDIA CUDA-Q | `--backend cudaq` | `pip install cudaq matplotlib` | macOS (Apple Silicon), Linux, Windows |
| NVIDIA cuQuantum | `--backend cuquantum` | `pip install qutip-cuquantum` + CUDA toolkit | Linux + NVIDIA GPU only |

All backends produce identical verification results — switching changes performance, not correctness. If a requested backend is unavailable, Q-Orca falls back to QuTiP and emits a `BACKEND_UNAVAILABLE` warning. See [Install](#install) for setup instructions.

### Stage 4 vs 4b — static vs dynamic

Stage 4 (`quantum.py`) checks your **declarations**: does the Markdown say this state is entangled? Does a CNOT gate lead to it? These are fast structural checks that catch obvious mistakes.
Expand Down Expand Up @@ -222,6 +261,9 @@ 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 |
| `--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) |

All 5 bundled examples pass `--strict` on every CI run (Python 3.10–3.13).

Expand Down Expand Up @@ -831,10 +873,17 @@ q_orca/
│ ├── quantum.py # Unitarity, no-cloning, entanglement
│ ├── superposition.py # Superposition coherence leak
│ └── dynamic.py # QuTiP circuit simulation
├── backends/
│ ├── base.py # BackendAdapter ABC
│ ├── registry.py # BackendRegistry with fallback logic
│ ├── qutip_backend.py # Default QuTiP adapter
│ ├── cuquantum_backend.py # NVIDIA cuQuantum adapter (optional)
│ └── cudaq_backend.py # NVIDIA CUDA-Q adapter (optional)
├── compiler/
│ ├── mermaid.py # Mermaid state diagram
│ ├── qasm.py # OpenQASM 3.0
│ └── qiskit.py # Qiskit Python script
│ ├── qiskit.py # Qiskit Python script
│ └── cudaq.py # NVIDIA CUDA-Q kernel
├── llm/
│ ├── provider.py # Abstract LLM provider interface
│ ├── anthropic.py # Anthropic provider
Expand All @@ -860,6 +909,7 @@ q_orca/
- ~~**Parameterized gates** — `Rx(θ)`, `Ry(θ)`, `Rz(θ)` with symbolic angles in the Markdown action table~~ ✅ **Shipped** — see [CHANGELOG](CHANGELOG.md) for the `0.3.3` entry
- ~~**Parameterized two-qubit gates** — `CRz`, `RXX`, `RYY`, `RZZ` with symbolic angles~~ ✅ **Shipped** — see [PR #5](../../pull/5)
- ~~**Hybrid classical/quantum transitions** — mid-circuit measurement + feedforward~~ ✅ **Shipped** — see [PR #5](../../pull/5)
- ~~**Pluggable execution backends** — cuQuantum GPU acceleration, CUDA-Q compilation target~~ ✅ **Shipped** — see [PR #7](../../pull/7)
- **Noise models** — depolarizing, amplitude damping, thermal noise in `## context`; propagate into Qiskit noise simulation
- **QASM 3.0 import** — parse existing `.qasm` files and lift them into Q-Orca state machines

Expand Down
32 changes: 16 additions & 16 deletions demos/hybrid_quantum_controller/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,18 @@ def refine_circuit(ctx, payload=None):
# Apply the deterministic fix
workspace.quantum_source = FIXED_BELL_ENTANGLER

print(f" [refine_circuit] Applied fix:")
print(f" [refine_circuit] + Added state expression for |psi> (Bell state)")
print(f" [refine_circuit] + Added |00_collapsed> and |11_collapsed> final states")
print(f" [refine_circuit] + Added measure_done transitions with collapse guards")
print(f" [refine_circuit] + Added probability guards and outcome actions")
print(" [refine_circuit] Applied fix:")
print(" [refine_circuit] + Added state expression for |psi> (Bell state)")
print(" [refine_circuit] + Added |00_collapsed> and |11_collapsed> final states")
print(" [refine_circuit] + Added measure_done transitions with collapse guards")
print(" [refine_circuit] + Added probability guards and outcome actions")

return {"iteration": iteration, "status": "refined"}


def compile_circuit(ctx, payload=None):
"""Compile the verified quantum machine to QASM, Qiskit, and Mermaid."""
print(f"\n [compile_circuit] Compiling to multiple targets...")
print("\n [compile_circuit] Compiling to multiple targets...")

machine = workspace.machine_def

Expand All @@ -258,13 +258,13 @@ def compile_circuit(ctx, payload=None):

def analyze_results(ctx, payload=None):
"""Display the compiled quantum circuit outputs."""
print(f"\n [analyze_results] Compiled outputs:\n")
print("\n [analyze_results] Compiled outputs:\n")

print(" --- OpenQASM 3.0 ---")
for line in workspace.qasm_output.strip().splitlines():
print(f" {line}")

print(f"\n --- Mermaid State Diagram ---")
print("\n --- Mermaid State Diagram ---")
for line in workspace.mermaid_output.strip().splitlines():
print(f" {line}")

Expand Down Expand Up @@ -360,11 +360,11 @@ async def on_transition(event: Event):
print("=" * 66)

# Start the experiment -- loads the broken quantum machine
print(f"\n--- [event] START_EXPERIMENT ---")
print("\n--- [event] START_EXPERIMENT ---")
await controller.send("START_EXPERIMENT", {"spec": "Bell state entangler"})

# Design complete -- trigger first verification
print(f"\n--- [event] DESIGN_COMPLETE ---")
print("\n--- [event] DESIGN_COMPLETE ---")
await controller.send("DESIGN_COMPLETE")

# ── 4. Inner refinement loop ──────────────────────────────────────────
Expand All @@ -375,19 +375,19 @@ async def on_transition(event: Event):
for _ in range(5): # safety limit
if workspace.is_valid:
# Verification passed -- move to compilation
print(f"\n--- [event] VERIFICATION_PASSED ---")
print("\n--- [event] VERIFICATION_PASSED ---")
await controller.send("VERIFICATION_PASSED")
break
else:
# Verification failed -- attempt refinement
print(f"\n--- [event] VERIFICATION_FAILED ---")
result = await controller.send("VERIFICATION_FAILED")
print("\n--- [event] VERIFICATION_FAILED ---")
await controller.send("VERIFICATION_FAILED")

if controller.state.leaf() == "failed":
break

# Refinement done -- re-verify
print(f"\n--- [event] REFINEMENT_COMPLETE ---")
print("\n--- [event] REFINEMENT_COMPLETE ---")
await controller.send("REFINEMENT_COMPLETE")

# ── 5. Compile and analyze ────────────────────────────────────────────
Expand All @@ -396,10 +396,10 @@ async def on_transition(event: Event):
print(" PHASE 5: Compile & analyze verified quantum circuit")
print("=" * 66)

print(f"\n--- [event] COMPILE_COMPLETE ---")
print("\n--- [event] COMPILE_COMPLETE ---")
await controller.send("COMPILE_COMPLETE")

print(f"\n--- [event] ANALYSIS_COMPLETE ---")
print("\n--- [event] ANALYSIS_COMPLETE ---")
await controller.send("ANALYSIS_COMPLETE")

# ── Result ────────────────────────────────────────────────────────────
Expand Down
8 changes: 3 additions & 5 deletions demos/quantum_evolve/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ async def _llm(system: str, user: str, temperature: float | None = None) -> str:
elapsed = time.time() - t0
print(f" {C.DIM}↳ {elapsed:.1f}s ({len(resp.content)} chars){C.RESET}", flush=True)
return resp.content
except (TimeoutError, OSError) as e:
except (TimeoutError, OSError):
elapsed = time.time() - t0
if attempt < LLM_MAX_RETRIES:
wait = 5 * attempt
Expand Down Expand Up @@ -657,7 +657,6 @@ async def _breed_next_gen(ctx, payload=None):
gen = ctx.get("generation", 0) + 1
parent_a, parent_b = evo.selected_parents
max_attempts = POPULATION_SIZE * 4
slots_needed = POPULATION_SIZE - ELITISM

_header(f"Breeding generation {gen}", "🧪")

Expand All @@ -675,7 +674,7 @@ async def _breed_next_gen(ctx, payload=None):
children: list[Individual] = [elite_copy]
seen: set[str] = {_normalize_source(elite.source)}
attempt = 0
bred_any = False # track whether at least one crossover/mutation succeeded


print()
while len(children) < POPULATION_SIZE and attempt < max_attempts:
Expand Down Expand Up @@ -710,7 +709,6 @@ async def _breed_next_gen(ctx, payload=None):
seen.add(norm)
child.id = f"g{gen}-{len(children)}"
children.append(child)
bred_any = True
_ok(f"{child.id}: accepted ({len(children)}/{POPULATION_SIZE})")
print()

Expand Down Expand Up @@ -760,7 +758,7 @@ def _report_best(ctx, payload=None):
f"Fitness: {best.fitness:.1f} / 100",
f"Valid: {'Yes' if best.is_valid else 'No'}",
f"Generation: {best.generation}",
f"",
"",
]

# Word-wrap rationale
Expand Down
53 changes: 53 additions & 0 deletions openspec/changes/execution-backends/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## 1. Backend Package Scaffolding

- [x] 1.1 Create `q_orca/backends/` package with `__init__.py` exporting `BackendRegistry`, `BackendAdapter`, `BackendResult`, `BackendUnavailableError`
- [x] 1.2 Implement `q_orca/backends/base.py` with abstract `BackendAdapter` class and `BackendResult` dataclass (fields: `name`, `version`, `errors`, `metadata`)
- [x] 1.3 Implement `q_orca/backends/registry.py` with `BackendRegistry` that maps names to adapters and resolves fallback on `BackendUnavailableError`

## 2. QuTiP Backend Adapter

- [x] 2.1 Implement `q_orca/backends/qutip_backend.py` — wraps existing `dynamic_verify()` logic, sets `AVAILABLE` based on QuTiP import, returns `BackendResult`
- [x] 2.2 Refactor `q_orca/verifier/dynamic.py` so `dynamic_verify()` is callable from the new QuTiP adapter without duplication

## 3. cuQuantum Backend Adapter

- [x] 3.1 Implement `q_orca/backends/cuquantum_backend.py` — sets `AVAILABLE = False` when `qutip_cuquantum` is missing, raises `BackendUnavailableError` from `verify()`
- [x] 3.2 Add `--gpu-count` and `--tensor-network` CLI flags to `verify` and `simulate` subparsers
- [x] 3.3 Pass `gpu_count` and `tensor_network` through `BackendRegistry` to cuQuantum adapter config

## 4. CUDA-Q Backend Adapter and Compiler

- [x] 4.1 Implement `q_orca/compiler/cudaq.py` with `compile_to_cudaq(machine) -> str` that emits a `@cudaq.kernel` Python script
- [x] 4.2 Add gate-mapping logic in `compile_to_cudaq`: H→`cudaq.h`, CNOT→`cudaq.x.ctrl`, X/Y/Z, Rx/Ry/Rz, measure→`mz`
- [x] 4.3 Implement `q_orca/backends/cudaq_backend.py` — sets `AVAILABLE` based on `cudaq` import, raises `BackendUnavailableError` when absent
- [x] 4.4 Add `cudaq` to the CLI `compile` format choices and wire to `compile_to_cudaq`

## 5. Verifier Integration

- [x] 5.1 Add `backend: str = "qutip"` field to `VerifyOptions` in `q_orca/verifier/__init__.py`
- [x] 5.2 Replace direct `dynamic_verify(machine)` call in `verify()` with `BackendRegistry.get(opts.backend).verify(machine)`, catching `BackendUnavailableError` and emitting `BACKEND_UNAVAILABLE` warning then retrying with QuTiP
- [x] 5.3 Add `--backend` flag to `verify` CLI subparser; pass it into `VerifyOptions`

## 6. Simulate Integration

- [x] 6.1 Add `--backend` flag to `simulate` CLI subparser
- [x] 6.2 Route Stage 4b in `simulate_machine()` through `BackendRegistry` using the selected backend
- [x] 6.3 Add `--cudaq-target` flag to `simulate` subparser and thread through to CUDA-Q adapter

## 7. JSON Output — Backend Metadata

- [x] 7.1 Inject `"backend": {"name": ..., "version": ...}` into `_cmd_verify` JSON output
- [x] 7.2 Inject `"backend": {"name": ..., "version": ...}` into `_cmd_simulate` JSON output

## 8. orca.yaml Config Extension

- [x] 8.1 Add `backend: str` and optional `cuquantum: dict` / `cudaq: dict` fields to `OrcaConfig` in `q_orca/config/types.py`
- [x] 8.2 Update `q_orca/config/loader.py` to parse the new fields from `orca.yaml`
- [x] 8.3 Implement `_resolve_backend(args, config)` helper in `cli.py` that merges CLI flag (priority) over config file value (fallback)

## 9. Tests

- [x] 9.1 Add `tests/test_backends.py` — unit tests for `BackendRegistry` fallback logic (mock `BackendUnavailableError`), `BACKEND_UNAVAILABLE` warning in `QVerificationResult`, and `BackendResult` metadata fields
- [x] 9.2 Add tests for `compile_to_cudaq` — Bell machine produces valid kernel string with `import cudaq`, `@cudaq.kernel`, correct gate calls
- [x] 9.3 Add CLI integration test: `q-orca verify --backend qutip --json` produces `"backend"` block; `q-orca verify --backend cuquantum --json` falls back to QuTiP with warning (mock cuquantum as unavailable)
- [x] 9.4 Run full test suite (`pytest`) and fix any regressions
19 changes: 19 additions & 0 deletions q_orca/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Q-Orca execution backends — pluggable verification and simulation backends."""

from q_orca.backends.base import BackendAdapter, BackendResult, BackendUnavailableError
from q_orca.backends.registry import BackendRegistry
from q_orca.backends.qutip_backend import qutip_backend
from q_orca.backends.cuquantum_backend import cuquantum_backend
from q_orca.backends.cudaq_backend import cudaq_backend

# Register all adapters; QuTiP is the fallback
BackendRegistry.register(qutip_backend, fallback=True)
BackendRegistry.register(cuquantum_backend)
BackendRegistry.register(cudaq_backend)

__all__ = [
"BackendAdapter",
"BackendResult",
"BackendUnavailableError",
"BackendRegistry",
]
44 changes: 44 additions & 0 deletions q_orca/backends/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Q-Orca backend adapter base classes."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, Optional

from q_orca.ast import QMachineDef
from q_orca.verifier.types import QVerificationResult


class BackendUnavailableError(Exception):
"""Raised when a backend's optional dependency is not installed."""


@dataclass
class BackendResult:
"""Metadata returned by a backend after verification."""
name: str
version: str = "unknown"
errors: list = field(default_factory=list)
metadata: Dict[str, Any] = field(default_factory=dict)


class BackendAdapter(ABC):
"""Abstract base class for all Q-Orca execution backends."""

#: Set to True at module load if the backend's dependencies are available.
AVAILABLE: bool = False

@property
@abstractmethod
def name(self) -> str:
"""Backend identifier (e.g. 'qutip', 'cuquantum', 'cudaq')."""

@property
def version(self) -> str:
"""Backend library version string."""
return "unknown"

@abstractmethod
def verify(self, machine: QMachineDef, options: Optional[Any] = None) -> tuple[QVerificationResult, BackendResult]:
"""Run verification and return (QVerificationResult, BackendResult)."""
Loading
Loading