Metadata-Version: 2.4
Name: cudaq-ibm-anton
Version: 0.1.0
Summary: Run CUDA-Q kernels on IBM Quantum with hardware-validated error mitigation
Author: Silicofeller Quantum
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/NVIDIA/ising
Keywords: quantum,cuda-q,qiskit,ibm-quantum,error-mitigation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: qiskit>=1.2
Requires-Dist: qiskit-ibm-runtime>=0.30
Provides-Extra: cudaq
Requires-Dist: cuda-quantum-cu13>=0.11; extra == "cudaq"
Provides-Extra: sim
Requires-Dist: qiskit-aer>=0.15; extra == "sim"
Provides-Extra: learned
Requires-Dist: scikit-learn>=1.3; extra == "learned"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: qiskit-aer>=0.15; extra == "dev"
Dynamic: license-file

# cudaq-ibm-anton

**Run CUDA-Q kernels on IBM Quantum, with error mitigation that's been measured.**

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)

CUDA-Q ships no IBM Quantum target — its backend list covers IonQ, Quantinuum,
IQM, OQC, Braket and QuEra, but not IBM. This package provides that path, plus a
mitigation layer where every claim is backed by hardware measurements, including
the ones that didn't work.

```python
import cudaq
from anton import AntonBackend

@cudaq.kernel
def ghz():
    q = cudaq.qvector(8)
    h(q[0])
    for i in range(7):
        x.ctrl(q[i], q[i + 1])

backend = AntonBackend("ibm_fez")
result = backend.run(ghz, shots=10000)
print(result.explain())
```

**[▶ Try it now — Colab quickstart, no IBM account needed](examples/quickstart.ipynb)**

```bash
pip install cudaq-ibm-anton[cudaq,sim]
```

## What it does

```
CUDA-Q kernel
  → cudaq.translate(format="openqasm2")
  → qiskit.qasm2.loads
  → transpile(optimization_level=3)      Qiskit remains the compilation baseline
  → circuit + hardware feature extraction
  → mitigation policy
  → IBM Quantum
  → post-processing
  → AntonResult
```

`cudaq` is optional. The package also accepts Qiskit circuits and OpenQASM 2
text, so the mitigation layer is usable without CUDA-Q.

## Measured on hardware

`ibm_fez` (156-qubit Heron r2), 215 rows across 30 IBM jobs. Full detail in
**[docs/FINDINGS.md](docs/FINDINGS.md)**.

**Readout correction is the reliable win** — 0.0992 → 0.0323 at n=8, ~15 sigma.
On `ibm_fez` readout error is 3.4× the two-qubit gate error.

**The right strategy depends on the circuit.** Interaction contrast
0.1373 ± 0.0150, **z = 9.2**. Few two-qubit gates → readout alone; gate-heavy →
readout + ZNE + DD.

**On 30 circuits never used to build the policy:**

| policy | mean error | vs fixed | oracle hits |
|---|---|---|---|
| raw | 0.1269 | — | — |
| fixed (readout) | 0.1052 | 0.0% | 8/30 |
| **two-qubit rule** | **0.0849** | **19.3%** | **22/30** |
| per-circuit oracle | 0.0777 | 26.1% | 30/30 |

Rule vs fixed: p = 0.0023, capturing 74% of the oracle's headroom.

**Dynamical decoupling alone is harmful** at every size we measured — it helps
only in combination, on gate-heavy circuits.

## The policy is a rule, and self-calibrating

`result.explain()` prints **`RULE, not AI`** on every run. We are explicit about
this because we tried the alternative: gradient-boosting models trained on
42,000 simulated circuits **did not beat the one-feature rule** (p = 0.43, and
directionally worse). That negative result and its diagnosis are in
[FINDINGS §5](docs/FINDINGS.md). The `LearnedPolicy` interface ships anyway — if
you beat the rule, that's a real result and we'd like to see it.

The threshold is not a magic constant. What governs the decision is *accumulated
infidelity* (`n_2q × e_2q`), so the gate-count threshold rescales with the
device's measured error:

```python
rule = TwoQubitRule.for_backend(backend.snapshot)   # automatic in AntonBackend
```

A device with half the gate error needs twice as many gates before ZNE has
enough coherent error to extrapolate against. On `ibm_fez` this reproduces the
validated threshold of 25 exactly, because `ibm_fez` is the reference.

> **Untested claim, stated plainly:** the rescaling is physically motivated but
> validated on **one device**. On `ibm_fez`, gate errors span only 1.7×, so
> scaled and unscaled forms are nearly identical and we cannot distinguish them.
> Cross-device behaviour is unverified. If you run this elsewhere, the data
> would be valuable.

## Three traps this package prevents

**Feature units.** Qiskit reports `instruction.duration` in the authored unit or
in `dt`, and `dt` is backend-specific. Mixing them is a silent 4× error. Every
duration here is converted to seconds, and `FEATURE_SCHEMA` declares a unit per
feature.

**Logical vs ISA features.** Computing features on the untranspiled circuit
shifts `isa_1q` by 2.6× (45 vs 117). `extract()` raises `NotTranspiledError`
rather than accepting one silently — testing operations against the backend's
actual basis, since a transpiled `AerSimulator` circuit is byte-identical to the
logical one.

**Degenerate synthetic features.** Modelling a device as homogeneous makes
`max_readout_err ≡ mean_readout_err`, which is a perfectly correlated pair. That
makes the whitened covariance singular and puts *every* real circuit at enormous
Mahalanobis distance — our OOD detector flagged 100% of real circuits for three
debugging cycles because of it. Real hardware is heterogeneous: on `ibm_fez` the
worst qubit carries **31× the median readout error**. See
[FINDINGS §7](docs/FINDINGS.md); this is probably the most transferable lesson
in the repository.

## API

```python
backend = AntonBackend("ibm_fez")            # or "aer" for local

decision = backend.predict(kernel)            # recommend, execute nothing
decision.action, decision.reason, decision.is_ai

result = backend.run(kernel, shots=10000)                    # policy chooses
result = backend.run(kernel, shots=10000, mitigation="raw")  # or force one

features = backend.features(kernel)           # 22 physical features, no family labels
snapshot = backend.snapshot                   # live calibration
snapshot.heterogeneity()                      # max/median spread per quantity
```

Modes: `auto` (default), `raw`, `readout`, `dd`, `zne`, `pec`. No technique is
silently bundled with another.

`run()` is a counts-based Sampler path and applies readout correction. ZNE and
PEC are expectation-value techniques — use `MitigationAction.apply()` with an
`EstimatorV2`.

## Provenance

Every run writes an `ExperimentRecord` to `~/.anton/experiments/`: git commit,
version, backend, calibration timestamp, OpenQASM 2, ISA depth, gate counts,
physical qubits, shots, mitigation parameters, raw and mitigated counts,
diagnostics, IBM job ids.

## Examples

| file | needs QPU |
|---|---|
| [`quickstart.ipynb`](examples/quickstart.ipynb) | no |
| `01_hello_ibm.py` | no (`--backend aer`) |
| `02_compare_mitigation.py` | no |
| `03_inspect_hardware.py` | account only, no QPU time |

## Tests

```bash
pip install cudaq-ibm-anton[dev] && pytest tests/
```

18 tests, no IBM account required.

## Scope

Physical (NISQ) error mitigation on unencoded circuits. **Not** quantum error
correction — QEC decoders consume detector syndromes from an encoded logical
qubit, which GHZ, QFT and VQE circuits do not produce. The two should not be
mixed and this package does not mix them.

## Contributing

The most useful contributions right now:

1. **Run the benchmark on a non-IBM-fez device.** The single biggest gap is
   cross-device validation of the threshold rescaling.
2. **Beat the rule.** `LearnedPolicy` is the interface; FINDINGS §5 explains
   where our attempt failed and why.
3. **Characterise PEC.** Exposed but never measured here.

## License

Apache-2.0. No third-party source vendored, no model weights bundled. See
[`NOTICE`](NOTICE).
