Metadata-Version: 2.4
Name: qiskit-zksf
Version: 0.2.2
Summary: Qiskit provider for ZKSF: run circuits on CPU, GPU or real quantum hardware with a documented accuracy statement on every result
Author-email: ZKSF <info@zksf.org>
License: MIT
Project-URL: Homepage, https://zksf.org
Project-URL: Documentation, https://zksf.org/docs
Project-URL: Console, https://app.zksf.org
Project-URL: Source, https://github.com/official-dvl/qiskit-zksf
Project-URL: Issues, https://github.com/official-dvl/qiskit-zksf/issues
Project-URL: Specification, https://doi.org/10.5281/zenodo.21851381
Project-URL: Verifier, https://pypi.org/project/zcc-verify/
Keywords: quantum,quantum computing,qiskit,qiskit provider,quantum circuit simulator,tensor network,matrix product state,stabilizer,QPU,error bounds,certification
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: qiskit>=1.0
Requires-Dist: qsim-sdk>=0.2
Dynamic: license-file

# qiskit-zksf

[![CI](https://github.com/official-dvl/qiskit-zksf/actions/workflows/ci.yml/badge.svg)](https://github.com/official-dvl/qiskit-zksf/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/qiskit-zksf.svg?v=3)](https://pypi.org/project/qiskit-zksf/)
[![Python](https://img.shields.io/pypi/pyversions/qiskit-zksf.svg?v=3)](https://pypi.org/project/qiskit-zksf/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.21851381-blue.svg)](https://doi.org/10.5281/zenodo.21851381)

Qiskit provider for **ZKSF** (Zero Kelvin Simulation Foundry). Point circuits you have
already written at classical simulators, GPU accelerators, or real quantum processors,
and get a documented accuracy statement back with every approximate result.

```bash
pip install qiskit-zksf
```

```python
from qiskit import QuantumCircuit
from qiskit_zksf import ZKSFProvider

qc = QuantumCircuit(40, 40)
qc.h(0)
for i in range(39):
    qc.cx(i, i + 1)
qc.measure(range(40), range(40))

backend = ZKSFProvider(token="...").backend("zksf_auto")
job = backend.run(qc, shots=1000)

print(job.result().get_counts())
print(job.error_info())     # how far that answer may be from the truth
```

The token comes from the console at [app.zksf.org](https://app.zksf.org), or from the
`ZKSF_TOKEN` environment variable.

## V2 primitives

Current Qiskit code builds a primitive rather than calling `backend.run()`, so both are
provided. They follow Qiskit's own semantics: one result per PUB, with parameter bindings
carried in the array shape.

```python
from qiskit_zksf import ZKSFProvider

provider = ZKSFProvider()

# Sampler: outcome distributions
sampler = provider.sampler()
result = sampler.run([qc], shots=1000).result()
result[0].data.meas.get_counts()
result[0].metadata["error_info"]        # the accuracy statement

# Estimator: expectation values
from qiskit.quantum_info import SparsePauliOp

estimator = provider.estimator()
result = estimator.run([(circuit, SparsePauliOp("ZZ"))]).result()
result[0].data.evs                       # the value
result[0].data.stds                      # the bound on it, not a guess
```

`stds` is not invented. Where the engine reports a measured bound it is passed through
unchanged, so an EstimatorV2 standard error means the same thing the protocol means.

`provider.estimator()` defaults to the Pauli propagation engine, which answers with an
expectation value directly and bounds it by the discarded coefficient mass.

### Parameter sweeps cost money

A local primitive will evaluate a thousand parameter bindings without comment. Here every
binding is a separate billed job, because the service has no batch endpoint yet, so a
sweep is refused rather than silently charged:

```python
sampler.run([(circuit, thousand_angles)])
# TooManyJobs: this run would submit 1000 separate billed jobs ...
```

Raise the limit deliberately when that is what you want:

```python
provider.sampler(max_jobs_per_run=200)
```

Combining an observable array with a parameter sweep in one PUB is not supported. It
raises rather than guessing a broadcast order, since guessing would mislabel every value
returned.

## Why this exists

Exact statevector simulation stops near 30 to 32 qubits, because state size grows as
`2^n`. Past that, every practical method is approximate: tensor networks truncate the
bond dimension, Pauli propagation truncates operator weight, real hardware substitutes
device noise for the ideal distribution.

Simulators do not normally tell you how much of the answer that cost you, even though
the error quantities exist inside the simulation. `job.error_info()` is that number.

```python
{'protocol': 'ZCC-v0.1',
 'method': 'MPS (quimb), measured discarded-weight bound',
 'truncation_weight': 2.220446049250313e-16,
 'error_bound': 2.1073424255447017e-08,
 'certified': True,
 'converged': True}
```

Any finished job can be minted into a public certificate that anyone can check without
an account, using [`zcc-verify`](https://pypi.org/project/zcc-verify/). The protocols are
specified in a citable paper: [doi.org/10.5281/zenodo.21851381](https://doi.org/10.5281/zenodo.21851381).

## Backends

| Backend | Engine | Qubits | Notes |
|---|---|---|---|
| `zksf_auto` | router picks | 128 | Default. Chooses the cheapest adequate engine |
| `zksf_exact_cpu` | `exact.cpu` | 30 | Exact statevector |
| `zksf_exact_gpu` | `exact.gpu` | 32 | Exact statevector on GPU, size-routed across two tiers |
| `zksf_mps` | `mps.quimb.cpu` | 128 | Tensor network, supports `certified=True` |
| `zksf_mps_aer` | `mps.aer.cpu` | 128 | Tensor network (Aer) |
| `zksf_clifford` | `clifford` | 5000 | Stabilizer, exact for Clifford circuits |
| `zksf_pauli` | `pauli.cpu` | 1024 | Heisenberg picture, needs an observable |
| `zksf_noisy` | `noisy.cpu` | 30 | Device noise model, supports `mitigate=True` |
| `zksf_rigetti` | `qpu.rigetti` | 108 | Rigetti Cepheus-1, real hardware |
| `zksf_ionq` | `qpu.ionq` | 36 | IonQ Forte-1, real hardware |

```python
provider = ZKSFProvider()
provider.backends()                      # all of them
provider.backends(min_num_qubits=100)    # only the ones that reach 100 qubits
provider.backends(hardware=True)         # only real quantum processors
```

Qubit counts mirror the limits the service enforces, so a circuit too large for an
engine fails at transpile time instead of after a round trip. The stabilizer backend
advertises only Clifford gates, so the transpiler will not hand it a `T` gate that Stim
cannot represent.

## Estimate before you spend

Qiskit has no equivalent concept, so this lives on the provider. It is free, instant, and
the only way to learn that a circuit would be **rejected** without submitting it.

```python
est = provider.estimate(qc, shots=1000)
print(est["engine"], est["predicted_cost_usd"], est["reason"])
```

## Asking for a measured bound

By default an approximate run is checked by convergence: the circuit is simulated again
at double the resource budget and the shift in outcome probabilities is reported. That is
evidence of accuracy, not a bound.

`certified=True` asks the tensor-network engine for a stronger statement. It runs with
state renormalization disabled, so the final state's norm deficit equals the total weight
discarded across every truncation, read directly off the result rather than estimated.

```python
job = provider.backend("zksf_mps").run(qc, shots=1000, certified=True)
job.error_info()["error_bound"]
```

## Rejections are a feature

A simulation whose own error bound would be vacuous is refused rather than returned, and
the refusal says what would make the circuit tractable.

```python
from qiskit_zksf import JobRejected

try:
    job.result()
except JobRejected as exc:
    print(exc)   # "intractable classically at this structure: ... Options: ..."
```

## Notes and limits

- **Several circuits become several jobs.** The service has no batch endpoint yet, so
  `backend.run([qc1, qc2])` submits them individually and each is billed separately.
- **No gradients.** This is a cloud job queue with per-job billing, so parameter-shift
  differentiation through it would be expensive and slow. Use a local simulator for
  optimization loops and this for the runs whose accuracy you need to state.
- **Hardware costs real money** and queues in hours, not seconds. Call `estimate()`
  first.

## Links

- Documentation: [zksf.org/docs](https://zksf.org/docs)
- Python SDK: [`qsim-sdk`](https://pypi.org/project/qsim-sdk/)
- Certificate checker: [`zcc-verify`](https://pypi.org/project/zcc-verify/)
- Android app: [Google Play](https://play.google.com/store/apps/details?id=com.quantumcomputing.app)
- Protocol paper: [10.5281/zenodo.21851381](https://doi.org/10.5281/zenodo.21851381)

## Licence

MIT.
