-
Notifications
You must be signed in to change notification settings - Fork 96
Qiskit Interop
The modern QDK provides interoperability with Qiskit circuits built upon the core Q# compiler infrastructure.
Leveraging the Q# compiler's capabilities for analysis, transformation, code generation, and simulation, the Qiskit interop provided by the QDK includes:
-
Resource estimation for their Qiskit circuits locally
- This includes a drop-in replacement for the Azure Quantum Resource Estimation APIs
- Q# Simulation of Qiskit circuits using Q#'s simulation capabilities
- QIR generation from Qiskit circuits leveraging the modern QDKs advanced code generation capabilities.
Detailed examples, potential errors, and usage with parameterized ciruits are demonstrated in the sample Qiskit interop notebook.
To get started, you'll need the qsharp
and qiskit
packages installed. As a convenience, you can install this via the qsharp
package as an optional dependency. You'll also wand the Q# widgets for visualization:
pip install "qsharp[qiskit,widgets]==1.8.2.dev0"
The resource estimation APIs have two primary ways to perform resource estimation
-
ResourceEstimatorBackend
class -
estimate
function
The estimate
function is a convenience wrapper which creates a ResourceEstimatorBackend
, runs it, and returns the result.
For a detailed set of examples, see the resource estimation with Qiskit sample notebook. These examples also show how to use the qsharp-widgets
for resource estimation in your own notebooks.
The modern QDK's resource estimation capabilities are an easy drop-in replacement for users of the existing Azure Quantum Resource Estimator tools provided by the azure-quantum
Python package.
Taking the following Azure Quantum example:
from qiskit.circuit.random import random_circuit
from azure.quantum import Workspace
from azure.quantum.qiskit import AzureQuantumProvider
workspace = Workspace(
resource_id = "",
location = "",
)
provider = AzureQuantumProvider(workspace)
backend = provider.get_backend('microsoft.estimator')
circuit = random_circuit(2, 2, measure=True)
result = backend.run(circuit).result()
To migrate to local estimation with the modern QDK, we'll replace azure.quantum.qiskit.AzureQuantumProvider
with ResourceEstimatorBackend
in qsharp.interop.qiskit
:
from qiskit.circuit.random import random_circuit
from qsharp.interop.qiskit import ResourceEstimatorBackend
circuit = random_circuit(2, 2, measure=True)
backend = ResourceEstimatorBackend()
result = backend.run(circuit).result()
The Qiskit interop is provided using OpenQASM 3. Qiskit cicuits are exported as OpenQASM which is parsed and compiled into a compatible format for Q#'s compiler. This enables running Qiskit circuits using the sparse simulator built into the qsharp
package.
When compiling the OpenQASM, the output semantics are translated to Qiskit's expected ordering and endianess.
from qiskit.circuit.random import random_circuit
from qsharp.interop.qiskit import QSharpBackend
circuit = random_circuit(2, 2, measure=True)
backend = QSharpBackend()
job = backend.run(circuit)
counts = job.result().get_counts()
print(counts)
In addition to regular circuits, newer Qiskit support for classical instructions is supported. An example can be found below in the QIR generation section.
QIR
generation, given that it is designed for running on hardware, imposes additional constraints on programs.
When generating QIR
, all registers must have been measured into. If there are any unused registers, an error will be raised. Additionally, attempting to generate QIR when the profile is set to Unrestricted
will raise an error. The Unrestricted
profile is only valid for simulation. Either TargetProfile.Base
or TargetProfile.Adaptive_RI
must be used. The target_profile
can be overridden in the backend.qir(...)
call to switch profiles.
from qiskit import ClassicalRegister, QuantumRegister
from qiskit.circuit import (
QuantumCircuit,
)
from qsharp import QSharpError, TargetProfile
qreg = QuantumRegister(3, name="q")
creg = ClassicalRegister(3, name="c")
qc = QuantumCircuit(qreg, creg)
qc.h([0, 1, 2])
qc.measure_all(add_bits=False)
with qc.switch(creg) as case:
with case(7):
qc.x(0)
with case(1, 2):
qc.z(1)
with case(case.DEFAULT):
qc.cx(0, 1)
qc.measure_all(add_bits=False)
backend = QSharpBackend()
print(backend.run(qc).result())
Using that same circuit, we can generate QIR which is used to run on quantum hardware.
# The default profile if not set is `TargetProfile.Unrestricted`
backend = QSharpBackend()
# here we override the default profile for the backend.
print(backend.qir(qc, target_profile=TargetProfile.Adaptive_RI))
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.
try:
backend.qir(qc, target_profile=TargetProfile.Base)
except QSharpError as e:
print(e)
Q# and Qiskit have differing semantics and targets, thus their features don't completely overlap. Below are some differences and unsupported capabilities:
- Hardware qubit IDs
- Timing and pulse control intrinsics
- Mutable arrays
-
ClassicalRegister
andClBit
can only hold qubit measurements and must be initialized with a measurement before use.