Metadata-Version: 2.4
Name: openquantum-sdk-qiskit
Version: 0.2.8
Summary: Qiskit Python SDK for Open Quantum
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: openquantum-sdk>=0.3.7
Requires-Dist: qiskit>=2.0.0

# openquantum-sdk-qiskit

Qiskit 2.x addon for the Open Quantum SDK.

## Installation

```bash
# Plugin (and core SDK)
pip install openquantum-sdk-qiskit

# or via extras:
pip install "openquantum-sdk[qiskit]"
```

## Authentication

The OpenQuantum Qiskit Service uses **saved accounts** for authentication.

### Option 1: Saved Account (Recommended)

Save your credentials once using the `ClientCredentials` model from the core SDK. This is the recommended, secure, one-time setup.

```python
from openquantum_sdk.auth import ClientCredentials
from openquantum_sdk.qiskit import OpenQuantumService

OpenQuantumService.save_account(
    name="default", # The name to refer to this account later
    creds=ClientCredentials(client_id="s_...", client_secret="e460c8..."),
    use_keyring=True, # Recommended for secure storage
)

# Then, load it automatically:
svc = OpenQuantumService()
```

### Option 2: Direct in Code

You can also pass the credentials directly when instantiating the service.

```python
from openquantum_sdk.qiskit import OpenQuantumService
from openquantum_sdk.auth import ClientCredentials

creds=ClientCredentials(client_id="s_...", client_secret="e460c8...")

svc = OpenQuantumService(creds=creds)
```

## Quickstart: SamplerV2

```python
# 1) Imports — just change import path from qiskit.* to openquantum.qiskit for SamplerV2 and EstimatorV2
from openquantum_sdk.qiskit import OpenQuantumService, SamplerV2, list_backends, get_backend

# Qiskit tools
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_histogram

# 2) Auth — load saved account
svc = OpenQuantumService()  # auto-loads saved account by default

# 3) Discover backends (client-side filters, optional)
bks = list_backends(service = svc, name="iqm", device_type="QPU", online=True)
print(bks)

# 4) Select a backend (by id, name, or short code)
backend = get_backend("iqm:emerald")

# 5) Build a circuit, transpile against the backend if desired
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

tqc = transpile(
    qc,
    backend=backend,
    optimization_level=1,
)

# 6) Create a Sampler and **run**

CONFIG = {
    "backend_class_id": "iqm:emerald",  # UUID or short code
    "job_subcategory_id": "phys:hds",  # Short code for Hamiltonian Dynamics Simulation
    "name": "bell-demo",  # optional job name prefix
    "execution_plan": "auto",  # "auto" (default), "public", or "private"
    "queue_priority": "auto",  # "auto" (default), "standard", "priority", or "instant"
}

sampler = SamplerV2(
    backend=backend,
    scheduler=svc.scheduler,
    config=CONFIG,
    export_format="qasm3",
)

# Run a job (circuit, pub for params, shots)
job = sampler.run([(tqc, None, 1000)])
result = job.result()
counts = result[0].data.meas.get_counts()
print(counts)

# 7) Plot
plot_histogram(counts)
```

## EstimatorV2 Quickstart

```python
# Import EstimatorV2
from openquantum_sdk.qiskit import EstimatorV2
from qiskit.quantum_info import SparsePauliOp
# Create an Estimator
est = EstimatorV2(
    backend=backend,
    scheduler=svc.scheduler,
    config=CONFIG
)
layout = tqc.layout
obs = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]).apply_layout(layout) # Define observations for estimator to evaluate
# Run (circuit, observables)
job = est.run([(tqc, obs)])
result = job.result()
print(f"Expectation value: {result[0].data.evs}")
```

## Execution Plans & Queue Priority

Both `create_sampler()` and `create_estimator()` accept `execution_plan` and `queue_priority` parameters:

```python
sampler = svc.create_sampler(
    backend=backend,
    execution_plan="private",    # "auto", "public", or "private"
    queue_priority="priority",   # "auto", "standard", "priority", or "instant"
)
```

| Parameter | Values | Default | Description |
|-----------|--------|---------|-------------|
| `execution_plan` | `"auto"`, `"public"`, `"private"` | `"auto"` | `"auto"` selects the cheapest plan (public). `"private"` uses dedicated resources at higher cost. |
| `queue_priority` | `"auto"`, `"standard"`, `"priority"`, `"instant"` | `"auto"` | `"auto"` selects the cheapest priority (standard). Higher priorities cost more but reduce queue wait time. |

`return_backend(name)` accepts an optional `config` dict for submission
defaults (e.g. organization, execution plan). The backend name is used as
`backend_class_id`. `job_subcategory_id` (and similar job metadata) lives
on the actual job creation — either at `backend.run(..., job_subcategory_id=...)`
or when calling `create_sampler`/`create_estimator`.

```python
backend = svc.return_backend("ionq:forte-1")
job = backend.run(qc, shots=1024)  # subcategory defaults to "oth:oth" here
```

You can override at run time or supply defaults via config for repeated use:

```python
backend = svc.return_backend("forte-1", config={
    "execution_plan": "private",
    "queue_priority": "instant",
})
# or pass per-run:
# job = backend.run(qc, shots=1024, job_subcategory_id="phys:oth")
```

## Job Subcategory

The `job_subcategory_id` parameter classifies the type of workload you're running. It defaults to `"oth:oth"` (Other) but **you should set it** to the most specific subcategory that matches your use case. See the [core SDK README](../../README.md) for the full list of subcategory short codes.

```python
sampler = svc.create_sampler(
    backend=backend,
    job_subcategory_id="phys:hds",  # Hamiltonian Dynamics Simulation
)
```

## API Reference

### `OpenQuantumService` Helpers

| Method | Description |
|--------|-------------|
| `OpenQuantumService()` | Main client for auth, discovery, and factory creation. |
| `save_account(...)` | Securely save credentials for auto-loading. |
| `list_backends(...)` | Discover available **BackendV2** objects (with client-side filters). |
| `get_backend(...)` | Retrieve a single **BackendV2** object. |

### Qiskit Primitives

| Class | Description |
|-------|-------------|
| `SamplerV2` | Qiskit 2.x Sampler compatible primitive that submits jobs to the Open Quantum platform. |

## Capabilities & Device Features

The SDK loads full device capabilities (from `constraint_data`) for every backend.

Key behaviors:

- The `backend.target` (used by Qiskit's transpiler) is built from `native_ops` + `supported_ops` + topology. It is also augmented with instructions such as `reset` and `barrier` when the device advertises the corresponding features (`"reset"`, `"barriers"`). This helps the transpiler produce circuits that the platform precompilers will accept.
- **Custom gates**: By default, most hardware backends do **not** allow user-defined `gate ... { ... }` definitions in submitted QASM.
  - If a device advertises `"custom_gates"` in its `features`, custom gates are permitted.
  - Otherwise, `backend.validate_circuit(circuit)` (and internal submission) will raise a clear error.
- **Reset, barriers, mid-circuit measurement, dynamic circuits**: The backend performs client-side validation (in `validate_circuit` and before submission) according to the advertised features. Using `reset` / `Barrier` / non-terminal measurements when the device does not advertise the feature raises a descriptive `ValueError` with remediation advice. The server precompilers remain the final authority and provide detailed errors.
- You are strongly encouraged to transpile before submission:

```python
tqc = transpile(circuit, backend=backend)
# then
backend.validate_circuit(tqc, shots=1024)
```

You can inspect device policy programmatically (new helpers beyond just custom gates):

```python
print(backend.allows_custom_gates())
print(backend.allows_reset())
print(backend.allows_barriers())
print(backend.allows_mid_circuit_measure())
print(backend.allows_dynamic_circuits())
print(backend.features)          # the raw set from constraint_data
backend.validate_circuit(my_circuit)   # raises on violation of any policy
```

The backend exposes `max_shots` directly (common Qiskit pattern), and a `limits` dict for other constraints:

```python
print(backend.max_shots)
print(backend.limits)   # contains min_shots, max_qubits_per_job, etc.

# Full validation including shots
backend.validate_circuit(circuit, shots=1024)
```
| `EstimatorV2` | Qiskit 2.x Estimator compatible primitive that submits jobs to the Open Quantum platform. |
