{
"cells": [
{
"cell_type": "markdown",
"id": "ae56fce0",
"metadata": {},
"source": [
"# Q# Interop with Qiskit"
]
},
{
"cell_type": "markdown",
"id": "2b838c23",
"metadata": {},
"source": [
"The modern QDK provides interoperability with Qiskit circuits built upon the core Q# compiler infrastructure.\n",
"\n",
"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 [modern QDKs advanced code generation capabilities](https://devblogs.microsoft.com/qsharp/integrated-hybrid-support-in-the-azure-quantum-development-kit/).\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"id": "4f761649",
"metadata": {},
"source": [
"## Running Qiskit circuits\n",
"The `QSharpBackend` backend is the main class to interact with for running circuits and generating QIR.\n",
"\n",
"To start, we'll set up a simple circuit with a prepared state."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e91dd2d7",
"metadata": {},
"outputs": [],
"source": [
"from qiskit import QuantumCircuit\n",
"import numpy as np\n",
"\n",
"circuit = QuantumCircuit(2, 2)\n",
"circuit.name = \"state_prep\"\n",
"\n",
"# State vector to initialize: |ψ⟩ = (|0⟩ - |1⟩) / √2\n",
"circuit.initialize([1 / np.sqrt(2), -1 / np.sqrt(2)], 0)\n",
"circuit.h(0)\n",
"circuit.measure(0, 0)\n",
"\n",
"circuit.prepare_state([1 / np.sqrt(2), -1 / np.sqrt(2)], 1)\n",
"circuit.h(1)\n",
"circuit.measure(1, 1)\n",
"\n",
"circuit.draw(output=\"text\")"
]
},
{
"cell_type": "markdown",
"id": "922f8420",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c69aeac",
"metadata": {},
"outputs": [],
"source": [
"from qsharp.interop.qiskit import QSharpBackend\n",
"\n",
"backend = QSharpBackend()\n",
"job = backend.run(circuit)\n",
"counts = job.result().get_counts()\n",
"print(counts)"
]
},
{
"cell_type": "markdown",
"id": "9c84df75",
"metadata": {},
"source": [
"## Parameterized Qiskit circuits\n",
"\n",
"Some circuits require parameters as input. To start, we'll define utility functions to create parameterized circuit(s)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d766661",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"import numpy as np\n",
"from qiskit import QuantumCircuit\n",
"from qiskit.circuit import Parameter\n",
"\n",
"\n",
"def get_theta_range(samples: int) -> List[float]:\n",
" return np.linspace(0, 2 * np.pi, samples)\n",
"\n",
"\n",
"def get_parameterized_circuit(n: int) -> QuantumCircuit:\n",
" theta = Parameter(\"θ\")\n",
" n = 5\n",
" qc = QuantumCircuit(n, 1)\n",
" qc.h(0)\n",
" for i in range(n - 1):\n",
" qc.cx(i, i + 1)\n",
" qc.barrier()\n",
" qc.rz(theta, range(n))\n",
" qc.barrier()\n",
"\n",
" for i in reversed(range(n - 1)):\n",
" qc.cx(i, i + 1)\n",
" qc.h(0)\n",
" qc.measure(0, 0)\n",
" return qc\n",
"\n",
"\n",
"def get_parameterized_circuits(n: int, theta_range: List[float]) -> List[QuantumCircuit]:\n",
" qc = get_parameterized_circuit(n)\n",
" qc.draw()\n",
" theta = qc.parameters[0]\n",
" circuits = [qc.assign_parameters({theta: theta_val}) for theta_val in theta_range]\n",
" return circuits"
]
},
{
"cell_type": "markdown",
"id": "d6b97850",
"metadata": {},
"source": [
"Attempting to run without binding all input will generate an error in the job."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4e05b81e",
"metadata": {},
"outputs": [],
"source": [
"from qsharp import QSharpError, TargetProfile\n",
"from qsharp.interop.qiskit import QSharpBackend\n",
"\n",
"circuit = get_parameterized_circuit(3)\n",
"backend = QSharpBackend()\n",
"try:\n",
" backend.qir(circuit, target_profile=TargetProfile.Base)\n",
"except QSharpError as e:\n",
" print(e)"
]
},
{
"cell_type": "markdown",
"id": "ee4d57b4",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9bb3eb0",
"metadata": {},
"outputs": [],
"source": [
"from qsharp.interop.qiskit import QSharpBackend\n",
"\n",
"circuit = get_parameterized_circuit(3)\n",
"backend = QSharpBackend()\n",
"\n",
"circuit.assign_parameters(\n",
" {\"θ\": \"0.5\"},\n",
" inplace=True,\n",
")\n",
"job = backend.run(circuit)\n",
"counts = job.result().get_counts()\n",
"print(counts)"
]
},
{
"cell_type": "markdown",
"id": "974b3131",
"metadata": {},
"source": [
"## Classical instructions in circuits"
]
},
{
"cell_type": "markdown",
"id": "42f916c9",
"metadata": {},
"source": [
"### Run Qiskit with classical instructions\n",
"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",
"\n",
"As an example, we can create a classical switch statement in Qiskit and run the program."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30d29199",
"metadata": {},
"outputs": [],
"source": [
"from qiskit import ClassicalRegister, QuantumRegister\n",
"from qiskit.circuit import (\n",
" QuantumCircuit,\n",
")\n",
"\n",
"from qsharp import QSharpError, TargetProfile\n",
"\n",
"qreg = QuantumRegister(3, name=\"q\")\n",
"creg = ClassicalRegister(3, name=\"c\")\n",
"qc = QuantumCircuit(qreg, creg)\n",
"qc.h([0, 1, 2])\n",
"qc.measure_all(add_bits=False)\n",
"\n",
"with qc.switch(creg) as case:\n",
" with case(7):\n",
" qc.x(0)\n",
" with case(1, 2):\n",
" qc.z(1)\n",
" with case(case.DEFAULT):\n",
" qc.cx(0, 1)\n",
"qc.measure_all(add_bits=False)\n",
"\n",
"backend = QSharpBackend()\n",
"\n",
"print(backend.run(qc).result())"
]
},
{
"cell_type": "markdown",
"id": "fea9051a",
"metadata": {},
"source": [
"Using that same circuit, we can generate QIR which is used to run on quantum hardware."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0081f6f0",
"metadata": {},
"outputs": [],
"source": [
"backend = QSharpBackend(target_profile=TargetProfile.Adaptive_RI)\n",
"print(backend.qir(qc))"
]
},
{
"cell_type": "markdown",
"id": "97184460",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06c31a67",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" backend.qir(qc, target_profile=TargetProfile.Base)\n",
"except QSharpError as e:\n",
" print(e)"
]
},
{
"cell_type": "markdown",
"id": "b06b7857",
"metadata": {},
"source": [
"## Errors\n",
"#### Unsupported language features, `QasmError`, and `QSharpError`\n",
"The modern 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",
"\n",
"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",
"\n",
"If the program can't be compiled to QIR, has invalid input bindings, or encounters a runtime error, a `QSharpError` is raised.\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"id": "d0f21006",
"metadata": {},
"source": [
"### Semantic Errors\n",
"It is still possible to create circuits that are semantically invalid. These will raise `QasmErrors` as the OpenQASM can't be compiled.\n",
"\n",
"\n",
"We'll look at examples of each scenario.\n"
]
},
{
"cell_type": "markdown",
"id": "6ebacf90",
"metadata": {},
"source": [
"#### General semantic errors\n",
"Most common semantic errors arise from unsupported features:\n",
"- No classical registers defined. If the circuit(s) being used do not measure into classical registers then the circuit is purely classical.\n",
"- Aliases were used for classical registers.\n",
"\n",
"Example, creating a circuit without any output:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b245c46c",
"metadata": {},
"outputs": [],
"source": [
"from qsharp.interop.qiskit import QasmError\n",
"\n",
"try:\n",
" circuit = QuantumCircuit(2)\n",
" circuit.x(0)\n",
" backend = QSharpBackend()\n",
" print(backend.run(circuit).result())\n",
"except QasmError as ex:\n",
" print(ex)"
]
},
{
"cell_type": "markdown",
"id": "f2da9168",
"metadata": {},
"source": [
"Example, using aliased classical registers:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "33fd3c4d",
"metadata": {},
"outputs": [],
"source": [
"from qsharp.interop.qiskit import QasmError\n",
"\n",
"try:\n",
" q = QuantumRegister(2, name=\"q\")\n",
" cr1 = ClassicalRegister(1, name=\"cr1\")\n",
" cr2 = ClassicalRegister(1, name=\"cr2\")\n",
" # Create a ClassicalRegister with bits from two different QuantumRegisters\n",
" # which is not supported by the Q# backend.\n",
" cr3 = ClassicalRegister(\n",
" name=\"cr3\",\n",
" bits=[\n",
" cr1[0],\n",
" cr2[0],\n",
" ],\n",
" )\n",
" qc = QuantumCircuit(q, cr1, cr2, cr3)\n",
"\n",
" backend = QSharpBackend(target_profile=TargetProfile.Base)\n",
" backend.qir(qc)\n",
"except QasmError as ex:\n",
" print(f\"Exception: {str(ex)}\")\n",
" # Print the cause of the exception if it exists.\n",
" # This will print the error message from Qiskit itself.\n",
" if ex.__cause__:\n",
" print(f\"Cause: {str(ex.__cause__)}\")"
]
},
{
"cell_type": "markdown",
"id": "62e33a40",
"metadata": {},
"source": [
"#### QIR generation semantic errors\n",
"\n",
"When targetting harware by compiling to QIR there are additional restrictions which may cause compilation errors. Most common scenarios:\n",
"- 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",
"- Not all bits in classical registers have been assigned to. Usually because there were no measurements, or extra registers were declared.\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "ac63ad88",
"metadata": {},
"source": [
"Example, generating QIR with `Unrestricted`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10d7dcb0",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" circuit = QuantumCircuit(1)\n",
" circuit.x(0)\n",
" circuit.measure_all()\n",
" backend = QSharpBackend()\n",
" print(backend.qir(circuit))\n",
"except ValueError as ex:\n",
" print(ex)"
]
},
{
"cell_type": "markdown",
"id": "fa219de1",
"metadata": {},
"source": [
"To avoid this issue, set the `target_profile` argument either in the `QSharpBackend` creation or in the `backend.qir` call."
]
},
{
"cell_type": "markdown",
"id": "3170569e",
"metadata": {},
"source": [
"When generating `QIR`, all output registers must be read into before generating QIR. Failure to do so results in a `QSharpError`.\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "071480db",
"metadata": {},
"outputs": [],
"source": [
"circuit = QuantumCircuit(2, 2)\n",
"circuit.x(0)\n",
"circuit.measure(0, 1)\n",
"backend = QSharpBackend(target_profile=TargetProfile.Base)\n",
"try:\n",
" print(backend.qir(circuit))\n",
"except QSharpError as ex:\n",
" print(ex)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
samples/python_interop/qiskit.ipynb
480lines · modepreview