microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
samples/python_interop/cirq_submission_to_azure.ipynb
224lines · modecode
| 1 | { |
| 2 | "cells": [ |
| 3 | { |
| 4 | "cell_type": "markdown", |
| 5 | "id": "fae381bf", |
| 6 | "metadata": {}, |
| 7 | "source": [ |
| 8 | "# Submitting Cirq Circuits to Azure Quantum with the QDK\n", |
| 9 | "\n", |
| 10 | "This notebook demonstrates how to run Cirq `Circuit` jobs on Azure Quantum using `AzureQuantumService` from the QDK." |
| 11 | ] |
| 12 | }, |
| 13 | { |
| 14 | "cell_type": "markdown", |
| 15 | "id": "0d696a7c", |
| 16 | "metadata": {}, |
| 17 | "source": [ |
| 18 | "The workflow demonstrated here:\n", |
| 19 | "\n", |
| 20 | "1. Build a Cirq `Circuit` with named measurement keys.\n", |
| 21 | "2. Reference an existing Azure Quantum workspace with `AzureQuantumService`.\n", |
| 22 | "3. Browse available targets via `service.targets()`.\n", |
| 23 | "4. Call `service.create_job(program=circuit, repetitions=..., target=...)` and fetch results (`job.results()`)." |
| 24 | ] |
| 25 | }, |
| 26 | { |
| 27 | "cell_type": "markdown", |
| 28 | "id": "22d78680", |
| 29 | "metadata": {}, |
| 30 | "source": [ |
| 31 | "## Prerequisites\n", |
| 32 | "\n", |
| 33 | "This notebook assumes the `qdk` package with Azure Quantum and Cirq support is installed. You can install everything with:" |
| 34 | ] |
| 35 | }, |
| 36 | { |
| 37 | "cell_type": "code", |
| 38 | "execution_count": null, |
| 39 | "id": "eb556730", |
| 40 | "metadata": {}, |
| 41 | "outputs": [], |
| 42 | "source": [ |
| 43 | "%pip install \"qdk[azure,cirq]\"" |
| 44 | ] |
| 45 | }, |
| 46 | { |
| 47 | "cell_type": "markdown", |
| 48 | "id": "20b9ed32", |
| 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 | "- Cirq 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": "7531d5a6", |
| 63 | "metadata": {}, |
| 64 | "outputs": [], |
| 65 | "source": [ |
| 66 | "import cirq, qdk, qdk.azure # should import without errors" |
| 67 | ] |
| 68 | }, |
| 69 | { |
| 70 | "cell_type": "markdown", |
| 71 | "id": "e2b111db", |
| 72 | "metadata": {}, |
| 73 | "source": [ |
| 74 | "## Build a simple Cirq circuit\n", |
| 75 | "\n", |
| 76 | "We start with a Bell-state circuit with two named measurement keys — one per qubit. Named keys are required so the result dictionary has clearly labeled registers." |
| 77 | ] |
| 78 | }, |
| 79 | { |
| 80 | "cell_type": "code", |
| 81 | "execution_count": null, |
| 82 | "id": "db4e96e3", |
| 83 | "metadata": {}, |
| 84 | "outputs": [], |
| 85 | "source": [ |
| 86 | "import cirq\n", |
| 87 | "\n", |
| 88 | "q0, q1 = cirq.LineQubit.range(2)\n", |
| 89 | "circuit = cirq.Circuit(\n", |
| 90 | " cirq.H(q0),\n", |
| 91 | " cirq.CNOT(q0, q1),\n", |
| 92 | " cirq.measure(q0, key=\"q0\"),\n", |
| 93 | " cirq.measure(q1, key=\"q1\"),\n", |
| 94 | ")\n", |
| 95 | "print(circuit)" |
| 96 | ] |
| 97 | }, |
| 98 | { |
| 99 | "cell_type": "markdown", |
| 100 | "id": "f582bc13", |
| 101 | "metadata": {}, |
| 102 | "source": [ |
| 103 | "## Configure Azure Quantum workspace connection\n", |
| 104 | "\n", |
| 105 | "To connect to an Azure workspace replace the following variables with your own values." |
| 106 | ] |
| 107 | }, |
| 108 | { |
| 109 | "cell_type": "code", |
| 110 | "execution_count": null, |
| 111 | "id": "0344527e", |
| 112 | "metadata": {}, |
| 113 | "outputs": [], |
| 114 | "source": [ |
| 115 | "subscription_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n", |
| 116 | "resource_group = 'myresourcegroup'\n", |
| 117 | "workspace_name = 'myworkspace'\n", |
| 118 | "location = 'westus'" |
| 119 | ] |
| 120 | }, |
| 121 | { |
| 122 | "cell_type": "markdown", |
| 123 | "id": "184b6e57", |
| 124 | "metadata": {}, |
| 125 | "source": [ |
| 126 | "## Submit the circuit to Azure Quantum\n", |
| 127 | "\n", |
| 128 | "`AzureQuantumService` exposes Azure Quantum targets as Cirq-compatible target objects. When you call `service.targets()`, the SDK returns one of two kinds of target:\n", |
| 129 | "\n", |
| 130 | "- **Provider-specific targets** — Some hardware vendors (e.g. IonQ, Quantinuum) ship dedicated Cirq target classes with native integration for their APIs, handling gate translation and result parsing using hardware-specific logic.\n", |
| 131 | "- **Generic QIR targets** — For any other target that accepts QIR input, the SDK automatically wraps it as an `AzureGenericQirCirqTarget`. These compile the Cirq circuit to OpenQASM 3 and then to QIR internally, so you don't have to manage those steps manually.\n", |
| 132 | "\n", |
| 133 | "In practice `service.targets()` selects the right type for each target — you use the same `service.create_job()` call regardless of which target type is returned." |
| 134 | ] |
| 135 | }, |
| 136 | { |
| 137 | "cell_type": "code", |
| 138 | "execution_count": null, |
| 139 | "id": "9f336702", |
| 140 | "metadata": {}, |
| 141 | "outputs": [], |
| 142 | "source": [ |
| 143 | "from qdk.azure import Workspace\n", |
| 144 | "from azure.quantum.cirq import AzureQuantumService\n", |
| 145 | "\n", |
| 146 | "workspace = Workspace(\n", |
| 147 | " subscription_id=subscription_id,\n", |
| 148 | " resource_group=resource_group,\n", |
| 149 | " name=workspace_name,\n", |
| 150 | " location=location,\n", |
| 151 | ")\n", |
| 152 | "\n", |
| 153 | "service = AzureQuantumService(workspace)\n", |
| 154 | "\n", |
| 155 | "# List available targets\n", |
| 156 | "for target in service.targets():\n", |
| 157 | " print(f\"{target.name:45s} {type(target).__name__}\")" |
| 158 | ] |
| 159 | }, |
| 160 | { |
| 161 | "cell_type": "code", |
| 162 | "execution_count": null, |
| 163 | "id": "417d7818", |
| 164 | "metadata": {}, |
| 165 | "outputs": [], |
| 166 | "source": [ |
| 167 | "from collections import Counter\n", |
| 168 | "\n", |
| 169 | "# Replace with any target name from the list above\n", |
| 170 | "target_name = \"rigetti.sim.qvm\"\n", |
| 171 | "\n", |
| 172 | "job = service.create_job(\n", |
| 173 | " program=circuit,\n", |
| 174 | " repetitions=100,\n", |
| 175 | " name=\"cirq-bell-job\",\n", |
| 176 | " target=target_name,\n", |
| 177 | ")\n", |
| 178 | "print(f\"Job {job.job_id()} submitted — waiting for results...\")\n", |
| 179 | "\n", |
| 180 | "result = job.results()\n", |
| 181 | "\n", |
| 182 | "# Combine separate measurement keys into joint bitstrings\n", |
| 183 | "keys = sorted(result.measurements.keys())\n", |
| 184 | "joint = Counter(\n", |
| 185 | " \"\".join(str(int(result.measurements[k][i][0])) for k in keys)\n", |
| 186 | " for i in range(len(result.measurements[keys[0]]))\n", |
| 187 | ")\n", |
| 188 | "total = sum(joint.values())\n", |
| 189 | "print(f\"\\nResults ({total} shots) [keys: {', '.join(keys)}]:\")\n", |
| 190 | "for bitstring, count in sorted(joint.items()):\n", |
| 191 | " print(f\" {bitstring}: {count:4d} ({count/total:.1%})\")" |
| 192 | ] |
| 193 | }, |
| 194 | { |
| 195 | "cell_type": "markdown", |
| 196 | "id": "1478e88a", |
| 197 | "metadata": {}, |
| 198 | "source": [ |
| 199 | "## Handling qubit loss on noisy hardware\n", |
| 200 | "\n", |
| 201 | "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 measurement arrays, which assume a fixed binary alphabet. The SDK therefore separates them automatically.\n", |
| 202 | "\n", |
| 203 | "The `cirq.ResultDict` returned by `job.results()` exposes two ways to access shots:\n", |
| 204 | "\n", |
| 205 | "| Field | What it contains |\n", |
| 206 | "|---|---|\n", |
| 207 | "| **`result.measurements[key]`** | NumPy int8 array of accepted shots only (no `\"-\"`), shape `(accepted_shots, num_qubits)` |\n", |
| 208 | "| **`result.raw_measurements()[key]`** | String array of all shots (loss shots have `\"-\"`), same key structure as `measurements` |\n", |
| 209 | "| **`result.raw_shots`** | The original shot objects exactly as returned by the backend |\n", |
| 210 | "\n", |
| 211 | "Use `result.measurements` for any downstream analysis that expects clean binary arrays. Use `result.raw_measurements()` and `result.raw_shots` to inspect loss patterns — for example, to calculate the overall loss rate or identify which qubit positions are being lost most frequently.\n", |
| 212 | "\n", |
| 213 | "> **Tip**: A high loss rate may indicate hardware instability or a circuit that is too deep for the current calibration." |
| 214 | ] |
| 215 | } |
| 216 | ], |
| 217 | "metadata": { |
| 218 | "language_info": { |
| 219 | "name": "python" |
| 220 | } |
| 221 | }, |
| 222 | "nbformat": 4, |
| 223 | "nbformat_minor": 5 |
| 224 | } |
| 225 | |