openquantum-sdk-pennylane 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: openquantum-sdk-pennylane
3
+ Version: 0.1.7
4
+ Summary: PennyLane Python SDK for Open Quantum
5
+ License: Apache-2.0
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: Apache Software License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: openquantum-sdk>=0.3.7
12
+ Requires-Dist: pennylane>=0.38
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Requires-Dist: pytest-cov; extra == "dev"
16
+ Requires-Dist: pytest-mock>=3.10; extra == "dev"
17
+ Requires-Dist: tomli; python_version < "3.11" and extra == "dev"
18
+
19
+ # PennyLane-Open Quantum Plugin
20
+
21
+ A [PennyLane](https://pennylane.ai/) plugin for the [Open Quantum](https://openquantum.com) quantum computing platform. Submit quantum circuits to real quantum hardware through the Open Quantum SDK.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install openquantum-sdk-pennylane
27
+ ```
28
+
29
+ **That's it.** Installing the package automatically registers the `openquantum.device` with PennyLane via Python entry points — no extra imports needed. Just `import pennylane as qml` and use `qml.device("openquantum.device", ...)`.
30
+
31
+ For development:
32
+
33
+ ```bash
34
+ pip install openquantum-sdk-pennylane[dev]
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ import pennylane as qml
41
+
42
+ # No need to import openquantum — the device registers automatically on install.
43
+ dev = qml.device(
44
+ "openquantum.device",
45
+ wires=2,
46
+ shots=1024,
47
+ backend="ionq:aria-1",
48
+ client_id="your_client_id",
49
+ client_secret="your_client_secret",
50
+ )
51
+
52
+ @qml.qnode(dev)
53
+ def bell_state():
54
+ qml.Hadamard(wires=0)
55
+ qml.CNOT(wires=[0, 1])
56
+ return qml.counts()
57
+
58
+ result = bell_state()
59
+ # The device shows backend status, credit cost, and prompts for confirmation:
60
+ # [Open Quantum] ● IonQ Aria-1 (ionq:aria-1): Online | queue: 0 job(s)
61
+ # [Open Quantum] Batch: 1 circuit(s) × 2 credit(s) each = 2 credit(s) total
62
+ # [Open Quantum] Proceed? [y/N] y
63
+ print(result) # {'00': 512, '11': 512}
64
+ ```
65
+
66
+ ## Configuration
67
+
68
+ ### Required Parameters
69
+
70
+ | Parameter | Description |
71
+ |-----------|-------------|
72
+ | `wires` | Number of qubits |
73
+ | `shots` | Number of measurement shots (required, no analytic mode) |
74
+ | `backend` | Backend class ID or short_code (e.g., `"ionq:aria-1"`) |
75
+
76
+ ### Authentication
77
+
78
+ The plugin supports three authentication methods, checked in order:
79
+
80
+ 1. **Pre-built SchedulerClient** -- pass `scheduler=` for full control.
81
+ 2. **Bearer token** -- pass `token=` or set `OPENQUANTUM_TOKEN`.
82
+ 3. **Client credentials** -- pass `client_id=` + `client_secret=` or set `OPENQUANTUM_CLIENT_ID` + `OPENQUANTUM_CLIENT_SECRET`.
83
+
84
+ | Parameter | Environment Variable | Description |
85
+ |-----------|---------------------|-------------|
86
+ | `token` | `OPENQUANTUM_TOKEN` | Bearer token |
87
+ | `client_id` | `OPENQUANTUM_CLIENT_ID` | OAuth2 client ID |
88
+ | `client_secret` | `OPENQUANTUM_CLIENT_SECRET` | OAuth2 client secret |
89
+ | `organization_id` | `OPENQUANTUM_ORGANIZATION_ID` | Organization UUID (optional if single org) |
90
+
91
+ ### Cost Control and Execution Flow
92
+
93
+ Before executing circuits, the device:
94
+
95
+ 1. **Checks backend status** — reports whether the target QPU is online, its queue depth, and lists online alternatives if it is offline or not accepting jobs.
96
+ 2. **Shows credit cost** — prepares the first circuit to get a real quote, then displays the per-circuit and total cost.
97
+ 3. **Prompts for confirmation** (terminal only) — asks `Proceed? [y/N]`. In Jupyter notebooks, execution proceeds immediately (use `auto_confirm=True` or interrupt the kernel to cancel).
98
+ 4. **Live status updates** — shows a spinner with job status during execution. While the job is pending, you can interrupt (Ctrl+C or Jupyter stop button) to cancel it on the platform. Once the job is queued, it can no longer be cancelled.
99
+
100
+ | Parameter | Default | Description |
101
+ |-----------|---------|-------------|
102
+ | `auto_confirm` | `False` | Skip the interactive cost confirmation prompt (terminal) and backend-unavailable warnings. Credits used are still printed to stdout. |
103
+
104
+ ```python
105
+ # Default: shows cost, prompts in terminal
106
+ dev = qml.device("openquantum.device", wires=2, shots=1024, backend="ionq:aria-1", ...)
107
+
108
+ # Skip prompts (still prints cost and status)
109
+ dev = qml.device("openquantum.device", wires=2, shots=1024, backend="ionq:aria-1", auto_confirm=True, ...)
110
+
111
+ # Check cumulative credits spent at any time
112
+ print(dev.total_credits_used)
113
+ ```
114
+
115
+ Example output during execution:
116
+ ```
117
+ [Open Quantum] ● IonQ Aria-1 (ionq:aria-1): Online | queue: 2 job(s)
118
+ [Open Quantum] Batch: 1 circuit(s) × 2 credit(s) each = 2 credit(s) total
119
+ [Open Quantum] Proceed? [y/N] y
120
+ [1/1] ⠹ Pending... (interrupt to cancel) (3s)
121
+ [1/1] Queued — can no longer be cancelled.
122
+ [1/1] ⠸ Running... (12s)
123
+ [Open Quantum] Batch complete: 2 credits used in 18s (session total: 2)
124
+ ```
125
+
126
+ Multi-circuit batch:
127
+ ```
128
+ [Open Quantum] Batch: 40 circuit(s) × 2 credit(s) each = 80 credit(s) total
129
+ [Open Quantum] Proceed? [y/N] y
130
+ [1/40] done (2/80 credits)
131
+ [2/40] done (4/80 credits)
132
+ ...
133
+ [Open Quantum] Batch complete: 80 credits used in 245s (session total: 80)
134
+ ```
135
+
136
+ ### Execution Plans & Queue Priority
137
+
138
+ Control cost and scheduling with `execution_plan` and `queue_priority`:
139
+
140
+ ```python
141
+ dev = qml.device(
142
+ "openquantum.device",
143
+ wires=2,
144
+ shots=1024,
145
+ backend="ionq:aria-1",
146
+ execution_plan="private", # "auto", "public", or "private"
147
+ queue_priority="priority", # "auto", "standard", "priority", or "instant"
148
+ ...
149
+ )
150
+ ```
151
+
152
+ | Parameter | Values | Default | Description |
153
+ |-----------|--------|---------|-------------|
154
+ | `execution_plan` | `"auto"`, `"public"`, `"private"` | `"auto"` | `"auto"` selects the cheapest plan (public). `"private"` uses dedicated resources at higher cost. |
155
+ | `queue_priority` | `"auto"`, `"standard"`, `"priority"`, `"instant"` | `"auto"` | `"auto"` selects the cheapest priority (standard). Higher priorities cost more but reduce queue wait time. |
156
+
157
+ ### Job Subcategory
158
+
159
+ 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.
160
+
161
+ ```python
162
+ dev = qml.device(
163
+ "openquantum.device",
164
+ wires=2,
165
+ shots=1024,
166
+ backend="ionq:aria-1",
167
+ job_subcategory_id="ml:qcl", # Quantum Classification
168
+ ...
169
+ )
170
+ ```
171
+
172
+ ### Other Optional Parameters
173
+
174
+ | Parameter | Default | Description |
175
+ |-----------|---------|-------------|
176
+ | `scheduler_url` | `https://scheduler.openquantum.com` | Scheduler API base URL |
177
+ | `management_url` | `https://management.openquantum.com` | Management API base URL |
178
+ | `keycloak_base` | `https://id.openquantum.com` | Keycloak auth base URL |
179
+ | `realm` | `"platform"` | Keycloak realm |
180
+ | `job_timeout_seconds` | `86400` | Max wait for job completion |
181
+
182
+ ## Supported Operations
183
+
184
+ ### Gates
185
+
186
+ PauliX, PauliY, PauliZ, Hadamard, CNOT, CZ, SWAP, RX, RY, RZ, PhaseShift, S, T, SX, Toffoli, CSWAP, CRX, CRY, CRZ, Identity
187
+
188
+ ### Measurements
189
+
190
+ - `qml.counts()` - Measurement counts
191
+ - `qml.expval()` - Expectation values
192
+ - `qml.var()` - Variance
193
+ - `qml.probs()` - Probabilities
194
+ - `qml.sample()` - Samples
195
+
196
+ All measurements require finite shots.
197
+
198
+ ## Examples
199
+
200
+ ### Expectation Values
201
+
202
+ ```python
203
+ @qml.qnode(dev)
204
+ def circuit(x):
205
+ qml.RX(x, wires=0)
206
+ qml.Hadamard(wires=1)
207
+ qml.CNOT(wires=[0, 1])
208
+ return qml.expval(qml.PauliZ(0))
209
+
210
+ result = circuit(0.543)
211
+ ```
212
+
213
+ ### VQE Workflow
214
+
215
+ ```python
216
+ # Use auto_confirm=True for iterative algorithms to avoid
217
+ # being prompted on every optimization step.
218
+ dev = qml.device("openquantum.device", wires=2, shots=4096,
219
+ backend="ionq:aria-1", auto_confirm=True, ...)
220
+
221
+ @qml.qnode(dev)
222
+ def cost_fn(params):
223
+ qml.RY(params[0], wires=0)
224
+ qml.RY(params[1], wires=1)
225
+ qml.CNOT(wires=[0, 1])
226
+ return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
227
+
228
+ opt = qml.GradientDescentOptimizer(stepsize=0.4)
229
+ params = np.array([0.5, 0.1])
230
+ for _ in range(100):
231
+ params = opt.step(cost_fn, params)
232
+
233
+ # Check total credits consumed
234
+ print(f"Total credits used: {dev.total_credits_used}")
235
+ ```
236
+
237
+ ## Device Features
238
+
239
+ The PennyLane device loads the backend's full `constraint_data` (including the `features` array). This enables:
240
+
241
+ - Early, client-side rejection of constructs the device does not advertise (e.g. `reset`, `barrier`, mid-circuit measurement patterns) during `qml.QNode` execution / `device.preprocess()`.
242
+ - The `decompose` step in preprocess is driven by the device's supported gate set (from `native_ops` / `supported_ops`).
243
+ - You can query features at runtime:
244
+
245
+ ```python
246
+ dev = qml.device("openquantum.device", wires=4, shots=1000, backend="iqm:emerald", ...)
247
+ print(dev.features)
248
+ print(dev.allows_reset()) # False for most real QPUs today
249
+ print(dev.allows_mid_circuit_measure())
250
+ print(dev.supports_feature("dynamic_circuits"))
251
+ print(dev.allows_barriers())
252
+ ```
253
+
254
+ The platform precompilers (in the worker) perform the definitive validation using the same feature list and will produce clear errors with source locations if something slips through.
255
+
256
+ See `sdk/docs/device-features.md` for the canonical list and `sdk/docs/capabilities.md` for the schema.
257
+
258
+ ## Comparison with Qiskit Plugin
259
+
260
+ | Feature | PennyLane | Qiskit |
261
+ |---------|-----------|--------|
262
+ | Execution plan | `execution_plan=` on device | `execution_plan=` on `create_sampler()` / `create_estimator()` / config dict |
263
+ | Queue priority | `queue_priority=` on device | `queue_priority=` on `create_sampler()` / `create_estimator()` / config dict |
264
+ | Job subcategory | `job_subcategory_id=` (default `"oth:oth"`) | `job_subcategory_id=` (default `"oth:oth"`) |
265
+ | Shots | Required, no default | Default `100` (sampler) / `1024` (backend) |
266
+ | Cost confirmation | Interactive prompt (`auto_confirm=False`) | No prompt (submits immediately) |
267
+ | Backend status check | Automatic pre-flight check | Not built-in |
268
+ | Interrupt handling | Ctrl+C cancels queued jobs | Not built-in |
269
+ | Auth | Token or client credentials (env vars or constructor) | Token, client credentials, or saved accounts with keyring |
270
+ | Credit tracking | `dev.total_credits_used` | Not built-in |
271
+ | QASM format | QASM 3.0 (automatic) | `"qasm2"` or `"qasm3"` (configurable) |
272
+
273
+ ## License
274
+
275
+ Apache License 2.0
@@ -0,0 +1,257 @@
1
+ # PennyLane-Open Quantum Plugin
2
+
3
+ A [PennyLane](https://pennylane.ai/) plugin for the [Open Quantum](https://openquantum.com) quantum computing platform. Submit quantum circuits to real quantum hardware through the Open Quantum SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install openquantum-sdk-pennylane
9
+ ```
10
+
11
+ **That's it.** Installing the package automatically registers the `openquantum.device` with PennyLane via Python entry points — no extra imports needed. Just `import pennylane as qml` and use `qml.device("openquantum.device", ...)`.
12
+
13
+ For development:
14
+
15
+ ```bash
16
+ pip install openquantum-sdk-pennylane[dev]
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ import pennylane as qml
23
+
24
+ # No need to import openquantum — the device registers automatically on install.
25
+ dev = qml.device(
26
+ "openquantum.device",
27
+ wires=2,
28
+ shots=1024,
29
+ backend="ionq:aria-1",
30
+ client_id="your_client_id",
31
+ client_secret="your_client_secret",
32
+ )
33
+
34
+ @qml.qnode(dev)
35
+ def bell_state():
36
+ qml.Hadamard(wires=0)
37
+ qml.CNOT(wires=[0, 1])
38
+ return qml.counts()
39
+
40
+ result = bell_state()
41
+ # The device shows backend status, credit cost, and prompts for confirmation:
42
+ # [Open Quantum] ● IonQ Aria-1 (ionq:aria-1): Online | queue: 0 job(s)
43
+ # [Open Quantum] Batch: 1 circuit(s) × 2 credit(s) each = 2 credit(s) total
44
+ # [Open Quantum] Proceed? [y/N] y
45
+ print(result) # {'00': 512, '11': 512}
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ ### Required Parameters
51
+
52
+ | Parameter | Description |
53
+ |-----------|-------------|
54
+ | `wires` | Number of qubits |
55
+ | `shots` | Number of measurement shots (required, no analytic mode) |
56
+ | `backend` | Backend class ID or short_code (e.g., `"ionq:aria-1"`) |
57
+
58
+ ### Authentication
59
+
60
+ The plugin supports three authentication methods, checked in order:
61
+
62
+ 1. **Pre-built SchedulerClient** -- pass `scheduler=` for full control.
63
+ 2. **Bearer token** -- pass `token=` or set `OPENQUANTUM_TOKEN`.
64
+ 3. **Client credentials** -- pass `client_id=` + `client_secret=` or set `OPENQUANTUM_CLIENT_ID` + `OPENQUANTUM_CLIENT_SECRET`.
65
+
66
+ | Parameter | Environment Variable | Description |
67
+ |-----------|---------------------|-------------|
68
+ | `token` | `OPENQUANTUM_TOKEN` | Bearer token |
69
+ | `client_id` | `OPENQUANTUM_CLIENT_ID` | OAuth2 client ID |
70
+ | `client_secret` | `OPENQUANTUM_CLIENT_SECRET` | OAuth2 client secret |
71
+ | `organization_id` | `OPENQUANTUM_ORGANIZATION_ID` | Organization UUID (optional if single org) |
72
+
73
+ ### Cost Control and Execution Flow
74
+
75
+ Before executing circuits, the device:
76
+
77
+ 1. **Checks backend status** — reports whether the target QPU is online, its queue depth, and lists online alternatives if it is offline or not accepting jobs.
78
+ 2. **Shows credit cost** — prepares the first circuit to get a real quote, then displays the per-circuit and total cost.
79
+ 3. **Prompts for confirmation** (terminal only) — asks `Proceed? [y/N]`. In Jupyter notebooks, execution proceeds immediately (use `auto_confirm=True` or interrupt the kernel to cancel).
80
+ 4. **Live status updates** — shows a spinner with job status during execution. While the job is pending, you can interrupt (Ctrl+C or Jupyter stop button) to cancel it on the platform. Once the job is queued, it can no longer be cancelled.
81
+
82
+ | Parameter | Default | Description |
83
+ |-----------|---------|-------------|
84
+ | `auto_confirm` | `False` | Skip the interactive cost confirmation prompt (terminal) and backend-unavailable warnings. Credits used are still printed to stdout. |
85
+
86
+ ```python
87
+ # Default: shows cost, prompts in terminal
88
+ dev = qml.device("openquantum.device", wires=2, shots=1024, backend="ionq:aria-1", ...)
89
+
90
+ # Skip prompts (still prints cost and status)
91
+ dev = qml.device("openquantum.device", wires=2, shots=1024, backend="ionq:aria-1", auto_confirm=True, ...)
92
+
93
+ # Check cumulative credits spent at any time
94
+ print(dev.total_credits_used)
95
+ ```
96
+
97
+ Example output during execution:
98
+ ```
99
+ [Open Quantum] ● IonQ Aria-1 (ionq:aria-1): Online | queue: 2 job(s)
100
+ [Open Quantum] Batch: 1 circuit(s) × 2 credit(s) each = 2 credit(s) total
101
+ [Open Quantum] Proceed? [y/N] y
102
+ [1/1] ⠹ Pending... (interrupt to cancel) (3s)
103
+ [1/1] Queued — can no longer be cancelled.
104
+ [1/1] ⠸ Running... (12s)
105
+ [Open Quantum] Batch complete: 2 credits used in 18s (session total: 2)
106
+ ```
107
+
108
+ Multi-circuit batch:
109
+ ```
110
+ [Open Quantum] Batch: 40 circuit(s) × 2 credit(s) each = 80 credit(s) total
111
+ [Open Quantum] Proceed? [y/N] y
112
+ [1/40] done (2/80 credits)
113
+ [2/40] done (4/80 credits)
114
+ ...
115
+ [Open Quantum] Batch complete: 80 credits used in 245s (session total: 80)
116
+ ```
117
+
118
+ ### Execution Plans & Queue Priority
119
+
120
+ Control cost and scheduling with `execution_plan` and `queue_priority`:
121
+
122
+ ```python
123
+ dev = qml.device(
124
+ "openquantum.device",
125
+ wires=2,
126
+ shots=1024,
127
+ backend="ionq:aria-1",
128
+ execution_plan="private", # "auto", "public", or "private"
129
+ queue_priority="priority", # "auto", "standard", "priority", or "instant"
130
+ ...
131
+ )
132
+ ```
133
+
134
+ | Parameter | Values | Default | Description |
135
+ |-----------|--------|---------|-------------|
136
+ | `execution_plan` | `"auto"`, `"public"`, `"private"` | `"auto"` | `"auto"` selects the cheapest plan (public). `"private"` uses dedicated resources at higher cost. |
137
+ | `queue_priority` | `"auto"`, `"standard"`, `"priority"`, `"instant"` | `"auto"` | `"auto"` selects the cheapest priority (standard). Higher priorities cost more but reduce queue wait time. |
138
+
139
+ ### Job Subcategory
140
+
141
+ 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.
142
+
143
+ ```python
144
+ dev = qml.device(
145
+ "openquantum.device",
146
+ wires=2,
147
+ shots=1024,
148
+ backend="ionq:aria-1",
149
+ job_subcategory_id="ml:qcl", # Quantum Classification
150
+ ...
151
+ )
152
+ ```
153
+
154
+ ### Other Optional Parameters
155
+
156
+ | Parameter | Default | Description |
157
+ |-----------|---------|-------------|
158
+ | `scheduler_url` | `https://scheduler.openquantum.com` | Scheduler API base URL |
159
+ | `management_url` | `https://management.openquantum.com` | Management API base URL |
160
+ | `keycloak_base` | `https://id.openquantum.com` | Keycloak auth base URL |
161
+ | `realm` | `"platform"` | Keycloak realm |
162
+ | `job_timeout_seconds` | `86400` | Max wait for job completion |
163
+
164
+ ## Supported Operations
165
+
166
+ ### Gates
167
+
168
+ PauliX, PauliY, PauliZ, Hadamard, CNOT, CZ, SWAP, RX, RY, RZ, PhaseShift, S, T, SX, Toffoli, CSWAP, CRX, CRY, CRZ, Identity
169
+
170
+ ### Measurements
171
+
172
+ - `qml.counts()` - Measurement counts
173
+ - `qml.expval()` - Expectation values
174
+ - `qml.var()` - Variance
175
+ - `qml.probs()` - Probabilities
176
+ - `qml.sample()` - Samples
177
+
178
+ All measurements require finite shots.
179
+
180
+ ## Examples
181
+
182
+ ### Expectation Values
183
+
184
+ ```python
185
+ @qml.qnode(dev)
186
+ def circuit(x):
187
+ qml.RX(x, wires=0)
188
+ qml.Hadamard(wires=1)
189
+ qml.CNOT(wires=[0, 1])
190
+ return qml.expval(qml.PauliZ(0))
191
+
192
+ result = circuit(0.543)
193
+ ```
194
+
195
+ ### VQE Workflow
196
+
197
+ ```python
198
+ # Use auto_confirm=True for iterative algorithms to avoid
199
+ # being prompted on every optimization step.
200
+ dev = qml.device("openquantum.device", wires=2, shots=4096,
201
+ backend="ionq:aria-1", auto_confirm=True, ...)
202
+
203
+ @qml.qnode(dev)
204
+ def cost_fn(params):
205
+ qml.RY(params[0], wires=0)
206
+ qml.RY(params[1], wires=1)
207
+ qml.CNOT(wires=[0, 1])
208
+ return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
209
+
210
+ opt = qml.GradientDescentOptimizer(stepsize=0.4)
211
+ params = np.array([0.5, 0.1])
212
+ for _ in range(100):
213
+ params = opt.step(cost_fn, params)
214
+
215
+ # Check total credits consumed
216
+ print(f"Total credits used: {dev.total_credits_used}")
217
+ ```
218
+
219
+ ## Device Features
220
+
221
+ The PennyLane device loads the backend's full `constraint_data` (including the `features` array). This enables:
222
+
223
+ - Early, client-side rejection of constructs the device does not advertise (e.g. `reset`, `barrier`, mid-circuit measurement patterns) during `qml.QNode` execution / `device.preprocess()`.
224
+ - The `decompose` step in preprocess is driven by the device's supported gate set (from `native_ops` / `supported_ops`).
225
+ - You can query features at runtime:
226
+
227
+ ```python
228
+ dev = qml.device("openquantum.device", wires=4, shots=1000, backend="iqm:emerald", ...)
229
+ print(dev.features)
230
+ print(dev.allows_reset()) # False for most real QPUs today
231
+ print(dev.allows_mid_circuit_measure())
232
+ print(dev.supports_feature("dynamic_circuits"))
233
+ print(dev.allows_barriers())
234
+ ```
235
+
236
+ The platform precompilers (in the worker) perform the definitive validation using the same feature list and will produce clear errors with source locations if something slips through.
237
+
238
+ See `sdk/docs/device-features.md` for the canonical list and `sdk/docs/capabilities.md` for the schema.
239
+
240
+ ## Comparison with Qiskit Plugin
241
+
242
+ | Feature | PennyLane | Qiskit |
243
+ |---------|-----------|--------|
244
+ | Execution plan | `execution_plan=` on device | `execution_plan=` on `create_sampler()` / `create_estimator()` / config dict |
245
+ | Queue priority | `queue_priority=` on device | `queue_priority=` on `create_sampler()` / `create_estimator()` / config dict |
246
+ | Job subcategory | `job_subcategory_id=` (default `"oth:oth"`) | `job_subcategory_id=` (default `"oth:oth"`) |
247
+ | Shots | Required, no default | Default `100` (sampler) / `1024` (backend) |
248
+ | Cost confirmation | Interactive prompt (`auto_confirm=False`) | No prompt (submits immediately) |
249
+ | Backend status check | Automatic pre-flight check | Not built-in |
250
+ | Interrupt handling | Ctrl+C cancels queued jobs | Not built-in |
251
+ | Auth | Token or client credentials (env vars or constructor) | Token, client credentials, or saved accounts with keyring |
252
+ | Credit tracking | `dev.total_credits_used` | Not built-in |
253
+ | QASM format | QASM 3.0 (automatic) | `"qasm2"` or `"qasm3"` (configurable) |
254
+
255
+ ## License
256
+
257
+ Apache License 2.0