microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
samples/python_interop/qiskit.ipynb
480lines · modecode
| 1 | { |
| 2 | "cells": [ |
| 3 | { |
| 4 | "cell_type": "markdown", |
| 5 | "id": "ae56fce0", |
| 6 | "metadata": {}, |
| 7 | "source": [ |
| 8 | "# QDK Interop with Qiskit" |
| 9 | ] |
| 10 | }, |
| 11 | { |
| 12 | "cell_type": "markdown", |
| 13 | "id": "2b838c23", |
| 14 | "metadata": {}, |
| 15 | "source": [ |
| 16 | "The QDK provides interoperability with Qiskit circuits built upon the core QDK compiler infrastructure.\n", |
| 17 | "\n", |
| 18 | "This core enables integration and local resource estimation without relying on external tools. Users are able to estimate resources for their Qiskit circuits locally (see the [resource estimation with Qiskit sample notebook](../../estimation/estimation-qiskit.ipynb)), leveraging the Q# compiler's capabilities for analysis, transformation, code generation, and simulation. This also enables the generation of QIR from Qiskit circuits leveraging the [QDKs advanced code generation capabilities](https://devblogs.microsoft.com/qsharp/integrated-hybrid-support-in-the-azure-quantum-development-kit/).\n", |
| 19 | "\n", |
| 20 | "This includes support for circuits with classical instructions available in Qiskit such as for loops, if statements, switch statements, while loops, binary expresssions, and more." |
| 21 | ] |
| 22 | }, |
| 23 | { |
| 24 | "cell_type": "markdown", |
| 25 | "id": "4f761649", |
| 26 | "metadata": {}, |
| 27 | "source": [ |
| 28 | "## Running Qiskit circuits\n", |
| 29 | "The `QSharpBackend` backend is the main class to interact with for running circuits and generating QIR.\n", |
| 30 | "\n", |
| 31 | "To start, we'll set up a simple circuit with a prepared state." |
| 32 | ] |
| 33 | }, |
| 34 | { |
| 35 | "cell_type": "code", |
| 36 | "execution_count": null, |
| 37 | "id": "e91dd2d7", |
| 38 | "metadata": {}, |
| 39 | "outputs": [], |
| 40 | "source": [ |
| 41 | "from qiskit import QuantumCircuit\n", |
| 42 | "import numpy as np\n", |
| 43 | "\n", |
| 44 | "circuit = QuantumCircuit(2, 2)\n", |
| 45 | "circuit.name = \"state_prep\"\n", |
| 46 | "\n", |
| 47 | "# State vector to initialize: |ψ⟩ = (|0⟩ - |1⟩) / √2\n", |
| 48 | "circuit.initialize([1 / np.sqrt(2), -1 / np.sqrt(2)], 0)\n", |
| 49 | "circuit.h(0)\n", |
| 50 | "circuit.measure(0, 0)\n", |
| 51 | "\n", |
| 52 | "circuit.prepare_state([1 / np.sqrt(2), -1 / np.sqrt(2)], 1)\n", |
| 53 | "circuit.h(1)\n", |
| 54 | "circuit.measure(1, 1)\n", |
| 55 | "\n", |
| 56 | "circuit.draw(output=\"text\")" |
| 57 | ] |
| 58 | }, |
| 59 | { |
| 60 | "cell_type": "markdown", |
| 61 | "id": "922f8420", |
| 62 | "metadata": {}, |
| 63 | "source": [ |
| 64 | "With the circuit created, we can run the circuit with Q#'s backend. By default, it will use the `Unrestricted` profile meaning anything is allowed for simulation." |
| 65 | ] |
| 66 | }, |
| 67 | { |
| 68 | "cell_type": "code", |
| 69 | "execution_count": null, |
| 70 | "id": "7c69aeac", |
| 71 | "metadata": {}, |
| 72 | "outputs": [], |
| 73 | "source": [ |
| 74 | "from qsharp.interop.qiskit import QSharpBackend\n", |
| 75 | "\n", |
| 76 | "backend = QSharpBackend()\n", |
| 77 | "job = backend.run(circuit)\n", |
| 78 | "counts = job.result().get_counts()\n", |
| 79 | "print(counts)" |
| 80 | ] |
| 81 | }, |
| 82 | { |
| 83 | "cell_type": "markdown", |
| 84 | "id": "9c84df75", |
| 85 | "metadata": {}, |
| 86 | "source": [ |
| 87 | "## Parameterized Qiskit circuits\n", |
| 88 | "\n", |
| 89 | "Some circuits require parameters as input. To start, we'll define utility functions to create parameterized circuit(s)." |
| 90 | ] |
| 91 | }, |
| 92 | { |
| 93 | "cell_type": "code", |
| 94 | "execution_count": null, |
| 95 | "id": "5d766661", |
| 96 | "metadata": {}, |
| 97 | "outputs": [], |
| 98 | "source": [ |
| 99 | "from typing import List\n", |
| 100 | "\n", |
| 101 | "import numpy as np\n", |
| 102 | "from qiskit import QuantumCircuit\n", |
| 103 | "from qiskit.circuit import Parameter\n", |
| 104 | "\n", |
| 105 | "\n", |
| 106 | "def get_theta_range(samples: int) -> List[float]:\n", |
| 107 | " return np.linspace(0, 2 * np.pi, samples)\n", |
| 108 | "\n", |
| 109 | "\n", |
| 110 | "def get_parameterized_circuit(n: int) -> QuantumCircuit:\n", |
| 111 | " theta = Parameter(\"θ\")\n", |
| 112 | " n = 5\n", |
| 113 | " qc = QuantumCircuit(n, 1)\n", |
| 114 | " qc.h(0)\n", |
| 115 | " for i in range(n - 1):\n", |
| 116 | " qc.cx(i, i + 1)\n", |
| 117 | " qc.barrier()\n", |
| 118 | " qc.rz(theta, range(n))\n", |
| 119 | " qc.barrier()\n", |
| 120 | "\n", |
| 121 | " for i in reversed(range(n - 1)):\n", |
| 122 | " qc.cx(i, i + 1)\n", |
| 123 | " qc.h(0)\n", |
| 124 | " qc.measure(0, 0)\n", |
| 125 | " return qc\n", |
| 126 | "\n", |
| 127 | "\n", |
| 128 | "def get_parameterized_circuits(n: int, theta_range: List[float]) -> List[QuantumCircuit]:\n", |
| 129 | " qc = get_parameterized_circuit(n)\n", |
| 130 | " qc.draw()\n", |
| 131 | " theta = qc.parameters[0]\n", |
| 132 | " circuits = [qc.assign_parameters({theta: theta_val}) for theta_val in theta_range]\n", |
| 133 | " return circuits" |
| 134 | ] |
| 135 | }, |
| 136 | { |
| 137 | "cell_type": "markdown", |
| 138 | "id": "d6b97850", |
| 139 | "metadata": {}, |
| 140 | "source": [ |
| 141 | "Attempting to run without binding all input will generate an error in the job." |
| 142 | ] |
| 143 | }, |
| 144 | { |
| 145 | "cell_type": "code", |
| 146 | "execution_count": null, |
| 147 | "id": "4e05b81e", |
| 148 | "metadata": {}, |
| 149 | "outputs": [], |
| 150 | "source": [ |
| 151 | "from qsharp import QSharpError, TargetProfile\n", |
| 152 | "from qsharp.interop.qiskit import QSharpBackend\n", |
| 153 | "\n", |
| 154 | "circuit = get_parameterized_circuit(3)\n", |
| 155 | "backend = QSharpBackend()\n", |
| 156 | "try:\n", |
| 157 | " backend.qir(circuit, target_profile=TargetProfile.Base)\n", |
| 158 | "except QSharpError as e:\n", |
| 159 | " print(e)" |
| 160 | ] |
| 161 | }, |
| 162 | { |
| 163 | "cell_type": "markdown", |
| 164 | "id": "ee4d57b4", |
| 165 | "metadata": {}, |
| 166 | "source": [ |
| 167 | "Any parameters must be bound before we can run the circuit. As we can see from the exception output, we must define the value for the input parameter `θ`. To do this, set the `params` argument to the `run` function." |
| 168 | ] |
| 169 | }, |
| 170 | { |
| 171 | "cell_type": "code", |
| 172 | "execution_count": null, |
| 173 | "id": "d9bb3eb0", |
| 174 | "metadata": {}, |
| 175 | "outputs": [], |
| 176 | "source": [ |
| 177 | "from qsharp.interop.qiskit import QSharpBackend\n", |
| 178 | "\n", |
| 179 | "circuit = get_parameterized_circuit(3)\n", |
| 180 | "backend = QSharpBackend()\n", |
| 181 | "\n", |
| 182 | "circuit.assign_parameters(\n", |
| 183 | " {\"θ\": 0.5},\n", |
| 184 | " inplace=True,\n", |
| 185 | ")\n", |
| 186 | "job = backend.run(circuit)\n", |
| 187 | "counts = job.result().get_counts()\n", |
| 188 | "print(counts)" |
| 189 | ] |
| 190 | }, |
| 191 | { |
| 192 | "cell_type": "markdown", |
| 193 | "id": "974b3131", |
| 194 | "metadata": {}, |
| 195 | "source": [ |
| 196 | "## Classical instructions in circuits" |
| 197 | ] |
| 198 | }, |
| 199 | { |
| 200 | "cell_type": "markdown", |
| 201 | "id": "42f916c9", |
| 202 | "metadata": {}, |
| 203 | "source": [ |
| 204 | "### Run Qiskit with classical instructions\n", |
| 205 | "Qiskit has begun implementing some classical computation support as they expand their OpenQASM 3 support. These constructs, insofar as Qiskit can export them, can be consumed by Q#.\n", |
| 206 | "\n", |
| 207 | "As an example, we can create a classical switch statement in Qiskit and run the program." |
| 208 | ] |
| 209 | }, |
| 210 | { |
| 211 | "cell_type": "code", |
| 212 | "execution_count": null, |
| 213 | "id": "30d29199", |
| 214 | "metadata": {}, |
| 215 | "outputs": [], |
| 216 | "source": [ |
| 217 | "from qiskit import ClassicalRegister, QuantumRegister\n", |
| 218 | "from qiskit.circuit import (\n", |
| 219 | " QuantumCircuit,\n", |
| 220 | ")\n", |
| 221 | "\n", |
| 222 | "from qsharp import QSharpError, TargetProfile\n", |
| 223 | "\n", |
| 224 | "qreg = QuantumRegister(3, name=\"q\")\n", |
| 225 | "creg = ClassicalRegister(3, name=\"c\")\n", |
| 226 | "qc = QuantumCircuit(qreg, creg)\n", |
| 227 | "qc.h([0, 1, 2])\n", |
| 228 | "qc.measure_all(add_bits=False)\n", |
| 229 | "\n", |
| 230 | "with qc.switch(creg) as case:\n", |
| 231 | " with case(7):\n", |
| 232 | " qc.x(0)\n", |
| 233 | " with case(1, 2):\n", |
| 234 | " qc.z(1)\n", |
| 235 | " with case(case.DEFAULT):\n", |
| 236 | " qc.cx(0, 1)\n", |
| 237 | "qc.measure_all(add_bits=False)\n", |
| 238 | "\n", |
| 239 | "backend = QSharpBackend()\n", |
| 240 | "\n", |
| 241 | "print(backend.run(qc).result())" |
| 242 | ] |
| 243 | }, |
| 244 | { |
| 245 | "cell_type": "markdown", |
| 246 | "id": "fea9051a", |
| 247 | "metadata": {}, |
| 248 | "source": [ |
| 249 | "Using that same circuit, we can generate QIR which is used to run on quantum hardware." |
| 250 | ] |
| 251 | }, |
| 252 | { |
| 253 | "cell_type": "code", |
| 254 | "execution_count": null, |
| 255 | "id": "0081f6f0", |
| 256 | "metadata": {}, |
| 257 | "outputs": [], |
| 258 | "source": [ |
| 259 | "backend = QSharpBackend(target_profile=TargetProfile.Adaptive_RI)\n", |
| 260 | "print(backend.qir(qc))" |
| 261 | ] |
| 262 | }, |
| 263 | { |
| 264 | "cell_type": "markdown", |
| 265 | "id": "97184460", |
| 266 | "metadata": {}, |
| 267 | "source": [ |
| 268 | "Not all programs can run on all hardware. Here we can try to target the `Base` profile, but we will get detailed errors on which parts of the program aren't supported." |
| 269 | ] |
| 270 | }, |
| 271 | { |
| 272 | "cell_type": "code", |
| 273 | "execution_count": null, |
| 274 | "id": "06c31a67", |
| 275 | "metadata": {}, |
| 276 | "outputs": [], |
| 277 | "source": [ |
| 278 | "try:\n", |
| 279 | " backend.qir(qc, target_profile=TargetProfile.Base)\n", |
| 280 | "except QSharpError as e:\n", |
| 281 | " print(e)" |
| 282 | ] |
| 283 | }, |
| 284 | { |
| 285 | "cell_type": "markdown", |
| 286 | "id": "b06b7857", |
| 287 | "metadata": {}, |
| 288 | "source": [ |
| 289 | "## Errors\n", |
| 290 | "#### Unsupported language features, `QasmError`, and `QSharpError`\n", |
| 291 | "The QDK's interop with Qiskit is based on Qiskit's OpenQASM 3 support. Qiskit supports a subset of OpenQASM 3 features which may cause issues during conversion.\n", |
| 292 | "\n", |
| 293 | "If the Qiskit OpenQASM `Exporter` or OpenQASM parser don't support the feature yet, a `QasmError` is raised prior to conversion. When an OpenQASM parsing failure occurs, this is likely an issue with the Qiskit libraries parsing and/or export functionality. Additionally, failure to transform the OpenQASM into Q#'s internal representation will throw a `QasmError`. This is most likely due to a semantically invalid OpenQASM program as input or an unsupported language feauture is being used.\n", |
| 294 | "\n", |
| 295 | "If the program can't be compiled to QIR, has invalid input bindings, or encounters a runtime error, a `QSharpError` is raised.\n", |
| 296 | "\n", |
| 297 | "If the backend configuration is not valid for a given operation, a `ValueError` may be raised. This is most likely caused by trying to generate QIR with the `Unrestricted` profile." |
| 298 | ] |
| 299 | }, |
| 300 | { |
| 301 | "cell_type": "markdown", |
| 302 | "id": "d0f21006", |
| 303 | "metadata": {}, |
| 304 | "source": [ |
| 305 | "### Semantic Errors\n", |
| 306 | "It is still possible to create circuits that are semantically invalid. These will raise `QasmErrors` as the OpenQASM can't be compiled.\n", |
| 307 | "\n", |
| 308 | "\n", |
| 309 | "We'll look at examples of each scenario.\n" |
| 310 | ] |
| 311 | }, |
| 312 | { |
| 313 | "cell_type": "markdown", |
| 314 | "id": "6ebacf90", |
| 315 | "metadata": {}, |
| 316 | "source": [ |
| 317 | "#### General semantic errors\n", |
| 318 | "Most common semantic errors arise from unsupported features:\n", |
| 319 | "- No classical registers defined. If the circuit(s) being used do not measure into classical registers then the circuit is purely classical.\n", |
| 320 | "- Aliases were used for classical registers.\n", |
| 321 | "\n", |
| 322 | "Example, creating a circuit without any output:" |
| 323 | ] |
| 324 | }, |
| 325 | { |
| 326 | "cell_type": "code", |
| 327 | "execution_count": null, |
| 328 | "id": "b245c46c", |
| 329 | "metadata": {}, |
| 330 | "outputs": [], |
| 331 | "source": [ |
| 332 | "from qsharp.interop.qiskit import QasmError\n", |
| 333 | "\n", |
| 334 | "try:\n", |
| 335 | " circuit = QuantumCircuit(2)\n", |
| 336 | " circuit.x(0)\n", |
| 337 | " backend = QSharpBackend()\n", |
| 338 | " print(backend.run(circuit).result())\n", |
| 339 | "except QasmError as ex:\n", |
| 340 | " print(ex)" |
| 341 | ] |
| 342 | }, |
| 343 | { |
| 344 | "cell_type": "markdown", |
| 345 | "id": "f2da9168", |
| 346 | "metadata": {}, |
| 347 | "source": [ |
| 348 | "Example, using aliased classical registers:" |
| 349 | ] |
| 350 | }, |
| 351 | { |
| 352 | "cell_type": "code", |
| 353 | "execution_count": null, |
| 354 | "id": "33fd3c4d", |
| 355 | "metadata": {}, |
| 356 | "outputs": [], |
| 357 | "source": [ |
| 358 | "from qsharp.interop.qiskit import QasmError\n", |
| 359 | "\n", |
| 360 | "try:\n", |
| 361 | " q = QuantumRegister(2, name=\"q\")\n", |
| 362 | " cr1 = ClassicalRegister(1, name=\"cr1\")\n", |
| 363 | " cr2 = ClassicalRegister(1, name=\"cr2\")\n", |
| 364 | " # Create a ClassicalRegister with bits from two different QuantumRegisters\n", |
| 365 | " # which is not supported by the Q# backend.\n", |
| 366 | " cr3 = ClassicalRegister(\n", |
| 367 | " name=\"cr3\",\n", |
| 368 | " bits=[\n", |
| 369 | " cr1[0],\n", |
| 370 | " cr2[0],\n", |
| 371 | " ],\n", |
| 372 | " )\n", |
| 373 | " qc = QuantumCircuit(q, cr1, cr2, cr3)\n", |
| 374 | "\n", |
| 375 | " backend = QSharpBackend(target_profile=TargetProfile.Base)\n", |
| 376 | " backend.qir(qc)\n", |
| 377 | "except QasmError as ex:\n", |
| 378 | " print(f\"Exception: {str(ex)}\")\n", |
| 379 | " # Print the cause of the exception if it exists.\n", |
| 380 | " # This will print the error message from Qiskit itself.\n", |
| 381 | " if ex.__cause__:\n", |
| 382 | " print(f\"Cause: {str(ex.__cause__)}\")" |
| 383 | ] |
| 384 | }, |
| 385 | { |
| 386 | "cell_type": "markdown", |
| 387 | "id": "62e33a40", |
| 388 | "metadata": {}, |
| 389 | "source": [ |
| 390 | "#### QIR generation semantic errors\n", |
| 391 | "\n", |
| 392 | "When targetting harware by compiling to QIR there are additional restrictions which may cause compilation errors. Most common scenarios:\n", |
| 393 | "- Trying to generate QIR when the profile is set to `Unrestricted`. `Unrestricted` is only valid for simulation. Either `TargetProfile.Base` or `TargetProfile.Adaptive_RI` must be used.\n", |
| 394 | "- Not all bits in classical registers have been assigned to. Usually because there were no measurements, or extra registers were declared.\n", |
| 395 | "\n" |
| 396 | ] |
| 397 | }, |
| 398 | { |
| 399 | "cell_type": "markdown", |
| 400 | "id": "ac63ad88", |
| 401 | "metadata": {}, |
| 402 | "source": [ |
| 403 | "Example, generating QIR with `Unrestricted`" |
| 404 | ] |
| 405 | }, |
| 406 | { |
| 407 | "cell_type": "code", |
| 408 | "execution_count": null, |
| 409 | "id": "10d7dcb0", |
| 410 | "metadata": {}, |
| 411 | "outputs": [], |
| 412 | "source": [ |
| 413 | "try:\n", |
| 414 | " circuit = QuantumCircuit(1)\n", |
| 415 | " circuit.x(0)\n", |
| 416 | " circuit.measure_all()\n", |
| 417 | " backend = QSharpBackend()\n", |
| 418 | " print(backend.qir(circuit))\n", |
| 419 | "except ValueError as ex:\n", |
| 420 | " print(ex)" |
| 421 | ] |
| 422 | }, |
| 423 | { |
| 424 | "cell_type": "markdown", |
| 425 | "id": "fa219de1", |
| 426 | "metadata": {}, |
| 427 | "source": [ |
| 428 | "To avoid this issue, set the `target_profile` argument either in the `QSharpBackend` creation or in the `backend.qir` call." |
| 429 | ] |
| 430 | }, |
| 431 | { |
| 432 | "cell_type": "markdown", |
| 433 | "id": "3170569e", |
| 434 | "metadata": {}, |
| 435 | "source": [ |
| 436 | "When generating `QIR`, all output registers must be read into before generating QIR. Failure to do so results in a `QSharpError`.\n", |
| 437 | "\n", |
| 438 | "In this next example, we declare two output bits, but only measure into one. This causes an error because result values can only be a side effect of measurement, and cannot be used like classical variables when compiling for hardware." |
| 439 | ] |
| 440 | }, |
| 441 | { |
| 442 | "cell_type": "code", |
| 443 | "execution_count": null, |
| 444 | "id": "071480db", |
| 445 | "metadata": {}, |
| 446 | "outputs": [], |
| 447 | "source": [ |
| 448 | "circuit = QuantumCircuit(2, 2)\n", |
| 449 | "circuit.x(0)\n", |
| 450 | "circuit.measure(0, 1)\n", |
| 451 | "backend = QSharpBackend(target_profile=TargetProfile.Base)\n", |
| 452 | "try:\n", |
| 453 | " print(backend.qir(circuit))\n", |
| 454 | "except QSharpError as ex:\n", |
| 455 | " print(ex)" |
| 456 | ] |
| 457 | } |
| 458 | ], |
| 459 | "metadata": { |
| 460 | "kernelspec": { |
| 461 | "display_name": ".venv", |
| 462 | "language": "python", |
| 463 | "name": "python3" |
| 464 | }, |
| 465 | "language_info": { |
| 466 | "codemirror_mode": { |
| 467 | "name": "ipython", |
| 468 | "version": 3 |
| 469 | }, |
| 470 | "file_extension": ".py", |
| 471 | "mimetype": "text/x-python", |
| 472 | "name": "python", |
| 473 | "nbconvert_exporter": "python", |
| 474 | "pygments_lexer": "ipython3", |
| 475 | "version": "3.13.3" |
| 476 | } |
| 477 | }, |
| 478 | "nbformat": 4, |
| 479 | "nbformat_minor": 5 |
| 480 | } |