microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
be82821236a00686b004bc7fe619ad16904f7997

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

samples/python_interop/qiskit_submission_to_azure.ipynb

280lines · modecode

1{
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "id": "90f546a9",
6 "metadata": {},
7 "source": [
8 "# Submitting Qiskit Circuits to Azure Quantum with the QDK\n",
9 "\n",
10 "This notebook demonstrates how to run Qiskit `QuantumCircuit` jobs on Azure Quantum using `AzureQuantumProvider` from the QDK. It also shows how to handle parameterized circuits by binding parameters prior to execution."
11 ]
12 },
13 {
14 "cell_type": "markdown",
15 "id": "2b838c23",
16 "metadata": {},
17 "source": [
18 "The workflow demonstrated here:\n",
19 "\n",
20 "1. Build a Qiskit `QuantumCircuit`.\n",
21 "2. Reference an existing Azure Quantum workspace with `qdk.azure.Workspace`.\n",
22 "3. Instantiate `AzureQuantumProvider(workspace)` and browse available backends (`provider.backends()`).\n",
23 "4. Pick a backend and call `backend.run(circuit, shots=...)` and fetch results (`job.result().get_counts()`)."
24 ]
25 },
26 {
27 "cell_type": "markdown",
28 "id": "004e5982",
29 "metadata": {},
30 "source": [
31 "## Prerequisites\n",
32 "\n",
33 "This notebook assumes the `qdk`, Azure Quantum integration, and Qiskit packages are installed. You can install everything (including the Azure + Qiskit extras) with:"
34 ]
35 },
36 {
37 "cell_type": "code",
38 "execution_count": null,
39 "id": "dfa160e8",
40 "metadata": {},
41 "outputs": [],
42 "source": [
43 "%pip install \"qdk[azure,qiskit]\""
44 ]
45 },
46 {
47 "cell_type": "markdown",
48 "id": "b7f37bcd",
49 "metadata": {},
50 "source": [
51 "This installs:\n",
52 "- The base qdk package (compiler, OpenQASM/QIR tooling)\n",
53 "- Azure Quantum client dependencies for submission\n",
54 "- Qiskit for circuit construction\n",
55 "\n",
56 "After installing, restart the notebook kernel if it was already running. You can verify installation with:"
57 ]
58 },
59 {
60 "cell_type": "code",
61 "execution_count": null,
62 "id": "f7446ed1",
63 "metadata": {},
64 "outputs": [],
65 "source": [
66 "import qiskit, qdk, qdk.azure # should import without errors"
67 ]
68 },
69 {
70 "cell_type": "markdown",
71 "id": "4f761649",
72 "metadata": {},
73 "source": [
74 "## Build a simple Qiskit circuit\n",
75 "\n",
76 "We start with a Bell-state circuit — a Hadamard on qubit 0 followed by a CNOT — which produces the entangled state $\\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$. After constructing the circuit we'll submit it to an Azure Quantum target."
77 ]
78 },
79 {
80 "cell_type": "code",
81 "execution_count": null,
82 "id": "e91dd2d7",
83 "metadata": {},
84 "outputs": [],
85 "source": [
86 "from qiskit import QuantumCircuit\n",
87 "\n",
88 "q = QuantumCircuit(2, 2)\n",
89 "q.h(0)\n",
90 "q.cx(0, 1)\n",
91 "q.measure([0, 1], [0, 1])\n",
92 "\n",
93 "circuit = q\n",
94 "circuit.draw(output=\"text\")"
95 ]
96 },
97 {
98 "cell_type": "markdown",
99 "id": "efd50528",
100 "metadata": {},
101 "source": [
102 "## Configure Azure Quantum workspace connection\n",
103 "\n",
104 "To connect to an Azure workspace replace the following variables with your own values."
105 ]
106 },
107 {
108 "cell_type": "code",
109 "execution_count": null,
110 "id": "54206752",
111 "metadata": {},
112 "outputs": [],
113 "source": [
114 "subscription_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n",
115 "resource_group = 'myresourcegroup'\n",
116 "workspace_name = 'myworkspace'\n",
117 "location = 'westus'"
118 ]
119 },
120 {
121 "cell_type": "markdown",
122 "id": "cdae59f7",
123 "metadata": {},
124 "source": [
125 "## Submit the circuit to Azure Quantum\n",
126 "\n",
127 "`AzureQuantumProvider` exposes Azure Quantum targets as Qiskit backends. When you call `provider.get_backend(target_name)`, the SDK returns one of two kinds of backend:\n",
128 "\n",
129 "- **Provider-specific backends** — Some hardware vendors (e.g. IonQ, Quantinuum) ship dedicated Qiskit backend classes that have native integration with their APIs. These handle gate translation and result parsing using hardware-specific logic.\n",
130 "- **Generic QIR backends** — For any other target that accepts QIR input, the SDK automatically wraps it as a generic backend. These compile the Qiskit circuit to OpenQASM 3 and then to QIR internally, so you don't have to manage those steps manually.\n",
131 "\n",
132 "In practice `get_backend()` selects the right type for you — you use the same call regardless of which backend type is returned."
133 ]
134 },
135 {
136 "cell_type": "code",
137 "execution_count": null,
138 "id": "d9a0f59b",
139 "metadata": {},
140 "outputs": [],
141 "source": [
142 "from qdk.azure import Workspace\n",
143 "from qdk.azure.qiskit import AzureQuantumProvider\n",
144 "\n",
145 "workspace = Workspace(\n",
146 " subscription_id=subscription_id,\n",
147 " resource_group=resource_group,\n",
148 " name=workspace_name,\n",
149 " location=location,\n",
150 ")\n",
151 "\n",
152 "provider = AzureQuantumProvider(workspace)\n",
153 "\n",
154 "# List available backends\n",
155 "for backend in provider.backends():\n",
156 " print(f\"{backend.name:45s} {type(backend).__name__}\")"
157 ]
158 },
159 {
160 "cell_type": "code",
161 "execution_count": null,
162 "id": "ce1b1809",
163 "metadata": {},
164 "outputs": [],
165 "source": [
166 "# Replace with any backend name from the list above\n",
167 "target_name = \"rigetti.sim.qvm\"\n",
168 "backend = provider.get_backend(target_name)\n",
169 "\n",
170 "job = backend.run(circuit, shots=100, job_name=\"qiskit-provider-job\")\n",
171 "print(f\"Job {job.job_id()} submitted — waiting for results...\")\n",
172 "\n",
173 "counts = job.result().get_counts()\n",
174 "total = sum(counts.values())\n",
175 "print(f\"\\nResults ({total} shots):\")\n",
176 "for bitstring, count in sorted(counts.items()):\n",
177 " print(f\" {bitstring}: {count:4d} ({count/total:.1%})\")"
178 ]
179 },
180 {
181 "cell_type": "markdown",
182 "id": "9c84df75",
183 "metadata": {},
184 "source": [
185 "## Parameterized Qiskit circuits\n",
186 "\n",
187 "Many algorithms use symbolic parameters. Before submitting to azure (or before compiling to QIR), all circuit parameters must be bound to numeric values. Below we build a simple entangling ladder, apply a rotation parameterized by `θ` across all qubits, then uncompute the entanglement and measure the first qubit."
188 ]
189 },
190 {
191 "cell_type": "code",
192 "execution_count": null,
193 "id": "5d766661",
194 "metadata": {},
195 "outputs": [],
196 "source": [
197 "from qiskit import QuantumCircuit\n",
198 "from qiskit.circuit import Parameter\n",
199 "\n",
200 "def get_parameterized_circuit(n = 5) -> QuantumCircuit:\n",
201 " theta = Parameter(\"θ\")\n",
202 " qc = QuantumCircuit(n, 1)\n",
203 " qc.h(0)\n",
204 " for i in range(n - 1):\n",
205 " qc.cx(i, i + 1)\n",
206 " qc.rz(theta, range(n))\n",
207 "\n",
208 " for i in reversed(range(n - 1)):\n",
209 " qc.cx(i, i + 1)\n",
210 " qc.h(0)\n",
211 " qc.measure(0, 0)\n",
212 " return qc\n",
213 "\n",
214 "# Build the symbolic (parameterized) circuit\n",
215 "parameterized_circuit = get_parameterized_circuit()\n",
216 "\n",
217 "# Bind θ to a numeric value, then visualize the bound circuit\n",
218 "bound_circuit = parameterized_circuit.assign_parameters({\"θ\": 0.5})\n",
219 "\n",
220 "bound_circuit.draw(output=\"text\")"
221 ]
222 },
223 {
224 "cell_type": "markdown",
225 "id": "ee4d57b4",
226 "metadata": {},
227 "source": [
228 "## Submitting a bound parameterized circuit\n",
229 "\n",
230 "Here we submit the `bound_circuit` from above using the recommended `AzureQuantumProvider` approach. The printed result shows the measurement counts. Change the value of `θ` (or loop over several values) to explore how the outcome distribution varies."
231 ]
232 },
233 {
234 "cell_type": "code",
235 "execution_count": null,
236 "id": "d9bb3eb0",
237 "metadata": {},
238 "outputs": [],
239 "source": [
240 "parameterized_job = backend.run(bound_circuit, shots=100, job_name=\"qiskit-parameterized-job\")\n",
241 "print(f\"Job {parameterized_job.job_id()} submitted — waiting for results...\")\n",
242 "\n",
243 "parameterized_counts = parameterized_job.result().get_counts()\n",
244 "print(parameterized_counts)"
245 ]
246 },
247 {
248 "cell_type": "markdown",
249 "id": "0ce5d167",
250 "metadata": {},
251 "source": [
252 "## Handling qubit loss on noisy hardware\n",
253 "\n",
254 "On some hardware backends — particularly neutral-atom and trapped-ion devices — a qubit may be lost before measurement (e.g. an atom is ejected from the trap). When this happens, the backend records `\"-\"` in the bitstring position for that qubit rather than `\"0\"` or `\"1\"`. Because loss shots contain non-binary characters, they cannot be included in standard counts or probability distributions, which assume a fixed binary alphabet. The SDK therefore separates them automatically.\n",
255 "\n",
256 "The result data (`job.result().results[0].data`) exposes two parallel sets of fields:\n",
257 "\n",
258 "| Field | What it contains |\n",
259 "|---|---|\n",
260 "| **`counts`** | Bitstring → shot count, accepted shots only (no `\"-\"`) |\n",
261 "| **`probabilities`** | Bitstring → empirical probability, accepted shots only |\n",
262 "| **`memory`** | Per-shot bitstring list, accepted shots only |\n",
263 "| **`raw_counts`** | Bitstring → shot count, all shots (loss shots have `\"-\"`) |\n",
264 "| **`raw_probabilities`** | Bitstring → empirical probability, all shots |\n",
265 "| **`raw_memory`** | Per-shot bitstring list, all shots |\n",
266 "\n",
267 "Use the **accepted** fields (`counts`, `probabilities`, `memory`) for any downstream analysis that expects clean binary bitstrings. Use the **raw** fields (`raw_counts`, `raw_probabilities`, `raw_memory`) to inspect loss patterns — for example, to calculate the overall loss rate or identify which qubit positions are being lost most frequently.\n",
268 "\n",
269 "> **Tip**: A high loss rate may indicate hardware instability or a circuit that is too deep for the current calibration."
270 ]
271 }
272 ],
273 "metadata": {
274 "language_info": {
275 "name": "python"
276 }
277 },
278 "nbformat": 4,
279 "nbformat_minor": 5
280}