Metadata-Version: 2.4
Name: qbron
Version: 0.1.0.dev0
Summary: Open-source SDK for running quantum workloads across any QPU backend.
Author: qbron
Author-email: qbron <hello@qbron.com>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Dist: openqasm3[parser]>=1.0.1
Requires-Dist: typer>=0.25.1
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# Qbron

> Unified Python SDK for quantum backends. Write your circuit once, route it to any QPU.

`qbron` is an open-source SDK that gives developers one API across IBM Quantum, IQM Resonance, AWS Braket, Azure Quantum, and local simulators — with cost transparency, smart routing, and EU data sovereignty. Apache 2.0.

The name is Swedish: `bron` = bridge.

> **Status:** early development. See [`CLAUDE.md`](./CLAUDE.md) for the architecture diagram and the build-order scoreboard.

## Install

```bash
pip install qbron        # or: uv add qbron
```

Requires Python 3.11+.

## Quick start

### Python

```python
from qbron.circuit import Circuit
from qbron.simulator import LocalSimulator

# Bell state on 2 qubits
bell = Circuit(num_qubits=2).h(0).cx(0, 1)

result = LocalSimulator(seed=42).run(bell, shots=1000)
print(result.counts)
# {'00': 480, '11': 520}    # entangled — never '01' or '10'
```

### CLI

```bash
cat > bell.qasm <<'EOF'
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
h q[0];
cx q[0], q[1];
EOF

qbron run bell.qasm --shots 1000 --seed 42
# 00: 480
# 11: 520
```

Pick a backend:

```bash
qbron run bell.qasm --backend=local         # pure-Python statevector (default)
qbron run bell.qasm --backend=mock          # priced fake remote (SEK)
qbron run bell.qasm --backend=aer           # Qiskit Aer simulator
qbron run bell.qasm --backend=ibm           # IBM Quantum (QBRON_IBM_TOKEN)
qbron run bell.qasm --backend=braket-local  # AWS Braket offline simulator
qbron run bell.qasm --backend=braket        # AWS Braket (AWS creds)
qbron run bell.qasm --backend=azure         # Azure Quantum (QBRON_AZURE_*)
qbron run bell.qasm --backend=auto          # cheapest compatible
```

### Explicit measurement

```python
# Measure only qubit 0; output bitstrings are 1 char wide.
half_bell = Circuit(num_qubits=2).h(0).cx(0, 1).measure(0)
LocalSimulator(seed=42).run(half_bell, shots=100).counts
# {'0': 48, '1': 52}
```

If a circuit has no `Measure` gates, every qubit is implicitly measured at the end (matches what most cloud APIs return).

### OpenQASM 3.0 round-trip

```python
from qbron.circuit import Circuit

bell = Circuit(num_qubits=2).h(0).cx(0, 1)
qasm = bell.to_qasm()
assert Circuit.from_qasm(qasm) == bell
```

The emitter is validated against the official `openqasm3` parser, and the round-trip is property-tested with Hypothesis.

### Cost estimation

```python
from qbron.mock_backend import MockRemoteBackend

backend = MockRemoteBackend()
cost = backend.estimate_cost(bell, shots=1000)
print(cost)
# Cost(currency='SEK', amount=10.0)
```

### Smart routing — `--backend=auto`

```python
from qbron.routing import route
from qbron.simulator import LocalSimulator
from qbron.mock_backend import MockRemoteBackend

# Pick the cheapest backend that can run the circuit. Costs in
# different currencies are normalised before comparison.
backend = route(
    bell, shots=1000,
    backends=[MockRemoteBackend(), LocalSimulator()],
    target_currency="USD",
)
result = backend.run(bell, shots=1000)
```

### Result caching

```python
from qbron.caching import CachedBackend
from qbron.mock_backend import MockRemoteBackend

backend = CachedBackend(MockRemoteBackend())
backend.run(bell, shots=1000)   # cache miss — actually runs
backend.run(bell, shots=1000)   # cache hit  — instant, free
```

### Error mitigation

```python
from qbron.mitigation import (
    ReadoutMitigatedBackend, ZNEBackend, calibrate_readout,
)

# Readout correction — calibrate once, apply to every run.
calibration = calibrate_readout(backend, num_qubits=2, shots=2000)
mitigated = ReadoutMitigatedBackend(backend, calibration)

# Zero-noise extrapolation — runs at multiple noise scales.
zne = ZNEBackend(backend, scale_factors=(1, 3, 5))
```

### Hybrid optimisation (VQE-style)

```python
import math
from qbron.circuit import Circuit
from qbron.hybrid import gradient_descent
from qbron.simulator import LocalSimulator

backend = LocalSimulator(seed=42)

def expectation_z(params):
    # ⟨Z⟩ on RY(θ)|0⟩ = cos(θ); minimum at θ = π.
    ansatz = Circuit(num_qubits=1).ry(0, params[0])
    counts = backend.run(ansatz, shots=4000).counts
    return (counts.get("0", 0) - counts.get("1", 0)) / 4000

result = gradient_descent(expectation_z, initial_params=[0.5])
print(result.params, result.value)  # ≈ [3.14], ≈ -1.0
```

### OpenTelemetry tracing

```python
from qbron.observability import TracedBackend

# Each run() emits a `qbron.backend.run` span with circuit shape,
# shot count, distinct outcomes, and exception info on failure.
traced = TracedBackend(backend)
traced.run(bell, shots=1000)
```

### Run on real IBM hardware

```python
from qbron.ibm_backend import IBMBackend

# Reads QBRON_IBM_TOKEN from the environment.
backend = IBMBackend.from_env("ibm_brisbane")
result = backend.run(bell, shots=1000)
```

For tests or local experiments without a real account, swap in a fake:

```python
from qiskit_ibm_runtime.fake_provider import FakeBrisbane
from qbron.ibm_backend import IBMBackend

backend = IBMBackend(FakeBrisbane())   # IBM-shaped, runs locally
result = backend.run(bell, shots=1000)
```

`QiskitBackend` accepts any Qiskit `BackendV2`, so AerSimulator, third-party Qiskit-compatible providers, and custom noise models all work:

```python
from qiskit_aer import AerSimulator
from qbron.qiskit_backend import QiskitBackend

result = QiskitBackend(AerSimulator()).run(bell, shots=1000)
```

### Run on AWS Braket and Azure Quantum

```python
from qbron.braket_backend import BraketBackend
from qbron.azure_backend import AzureBackend

# AWS Braket — uses standard AWS env vars (AWS_ACCESS_KEY_ID etc.)
braket = BraketBackend.from_env(
    "arn:aws:braket:::device/quantum-simulator/amazon/sv1"
)

# Azure Quantum — needs QBRON_AZURE_SUBSCRIPTION_ID, RESOURCE_GROUP,
# WORKSPACE_NAME, LOCATION
azure = AzureBackend.from_env("ionq.simulator")
```

For local Braket experimentation without AWS:

```python
from qiskit_braket_provider import BraketLocalBackend
from qbron.braket_backend import BraketBackend

result = BraketBackend(BraketLocalBackend()).run(bell, shots=1000)
```

## Supported

| | |
|--|--|
| **Gates** | H · X · Y · Z · S · S† · T · T† · CX · RX(θ) · RY(θ) · RZ(θ) · Measure |
| **Backends** | `LocalSimulator` · `MockRemoteBackend` · `QiskitBackend` · `IBMBackend` · `BraketBackend` · `AzureBackend` · `IQMBackend` |
| **Format** | OpenQASM 3.0 in/out (validated by the official parser) |
| **Correctness** | Cross-validated against Qiskit Aer (TVD < 5%) |

Roadmap and architecture: [`CLAUDE.md`](./CLAUDE.md).

## Develop

```bash
uv sync
uv run pytest
```

TDD discipline: every change starts with a failing test.

## License

Apache 2.0 — see [`LICENSE`](./LICENSE).
