> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.conductorquantum.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.conductorquantum.com/_mcp/server.

# QubiC Example

[`coda-qubic`](https://github.com/conductorquantum/coda-qubic) is an example `coda-node` integration for [QubiC](https://lbl-qubic.gitlab.io/) systems. Use it as a reference for connecting your own QPU with a device YAML, executor factory, native IR translator, hardware runner, and node startup flow.

## About QubiC

[QubiC](https://lbl-qubic.gitlab.io/) is an open-source FPGA-based control and measurement system for superconducting quantum processors, developed at Lawrence Berkeley National Laboratory's Advanced Quantum Testbed. It provides a modular hardware and software stack for implementing qubit control, measurement, and characterization protocols.

For more details, see the [QubiC documentation](https://lbl-qubic.gitlab.io/) and the [QubiC paper on arXiv](https://arxiv.org/abs/2101.00071).

## Runtime Flow

At runtime, the integration follows this flow:

* Coda sends a compiled `NativeGateIR` job to the node.
* `coda-qubic` translates the IR into QubiC gate programs.
* The runner executes the program on QubiC hardware, a QubiC RPC server, or a simulator.

> **Note**
>
> `coda-qubic` requires Python 3.12 or newer. `coda-node` itself supports Python 3.11 or newer.

## What coda-qubic provides

* `coda_qubic.executor_factory:create_executor` for `coda-node`
* `QubiCConfig` for validating device YAML
* `QubiCJobRunner`, which implements `executor.run(ir, shots)`
* Translation from Coda `NativeGateIR` to QubiC gate-level programs
* RPC, local hardware, pulse simulator, and Qiskit noisy simulator modes
* Optional cancellation support through `cancel_current_job()`

## Install

Clone the integration and install dependencies:

```bash
git clone https://github.com/conductorquantum/coda-qubic.git
cd coda-qubic
uv sync --dev
```

For QubiC hardware, install the QubiC stack as well:

```bash
./scripts/install-qubic-stack.sh
```

For the Qiskit simulator path, install the optional Qiskit dependency group:

```bash
uv pip install 'coda-qubic[qiskit]'
```

## Configure a QubiC lab

For a real QubiC setup, collect these files from the lab:

* `qubitcfg.json`: qubit calibration and gate definitions
* `channel_config.json`: FPGA channel mapping
* `classifier.pkl` or classifier JSON: readout discrimination

Create `site/device.yaml` next to those files:

```yaml
target: cnot
num_qubits: 20
calibration_path: ./qubitcfg.json
channel_config_path: ./channel_config.json
classifier_path: ./classifier.pkl

runner_mode: rpc
rpc_host: 192.168.1.120
rpc_port: 9095
```

All file paths are resolved relative to the YAML file. The `num_qubits` value must match the device derived from the calibration file.

## Simulator configuration

For end-to-end testing without hardware or QubiC vendor dependencies, use Qiskit noisy simulation:

```yaml
framework: qubic
target: cnot
num_qubits: 3
runner_mode: qiskit_sim

# Optional noise parameters
# single_qubit_error_rate: 0.001
# two_qubit_error_rate: 0.01
# measurement_error_rate: 0.01
```

This mode reports a synthetic device and executes jobs with Qiskit AerSimulator.

## Validate locally

Validate the YAML:

```bash
uv run python -c "
from coda_qubic.config import QubiCConfig

config = QubiCConfig.from_yaml('site/device.yaml')
print('OK:', config.target, config.num_qubits, 'qubits')
"
```

Run a small circuit through the QubiC executor:

```bash
uv run python -c "
import asyncio
from coda_node.server.ir import GateOp, IRMetadata, NativeGateIR
from coda_qubic.config import QubiCConfig
from coda_qubic.executor_factory import build_executor

config = QubiCConfig.from_yaml('site/device.yaml')
executor = build_executor(config)

ir = NativeGateIR(
    num_qubits=config.num_qubits,
    target=config.target,
    gates=[
        GateOp(gate='x90', qubits=[0], params=[]),
        GateOp(gate='cnot', qubits=[0, 1], params=[]),
    ],
    measurements=[0, 1],
    metadata=IRMetadata(source_hash='test', compiled_at='2026-04-27T00:00:00Z'),
)

result = asyncio.run(executor.run(ir, shots=100))
print(result.counts)
"
```

## Run through coda-node

If `coda-qubic` is the only backend package installed and `./site/device.yaml` exists, `coda-node` can discover the executor factory automatically:

```bash
uv run coda-node start --token <node-token>
```

To be explicit:

```bash
CODA_EXECUTOR_FACTORY=coda_qubic.executor_factory:create_executor \
CODA_DEVICE_CONFIG=./site/device.yaml \
uv run coda-node start --token <node-token>
```

If your node token requires VPN mode, OpenVPN may need elevated permissions to create the tunnel interface:

```bash
sudo env CODA_DEVICE_CONFIG=./site/device.yaml uv run coda-node start --token <node-token>
```

After first connect, credentials are persisted. Restart without a token:

```bash
uv run coda-node start
```

## How the example maps to the Node model

`coda-qubic` is a reference for the main integration points:

* Device config: `QubiCConfig.from_yaml("site/device.yaml")`
* Factory: `coda_qubic.executor_factory:create_executor`
* Executor: `QubiCJobRunner.run(ir, shots)`
* Translation: Coda native gate IR to QubiC gate programs
* Results: QubiC counts normalized into `ExecutionResult`
* Cancellation: `cancel_current_job()` sets a cancel flag checked by the runner

Use the same shape for your own backend package, replacing QubiC-specific config, translation, and hardware calls with your device stack.