{
  "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
}
