Source code for qrisp.interface.provider_backends.aqt_backend
"""
********************************************************************************
* Copyright (c) 2025 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""
import qiskit
from qrisp.interface import VirtualBackend, BatchedBackend
[docs]
class AQTBackend(BatchedBackend):
"""
This class instantiates a :ref:`VirtualBackend` using an AQT backend.
This allows easy access to AQT backends through the qrisp interface.
Using this backend requires addtional dependencies: ``pip install qiskit-aqt-provider``.
Parameters
----------
api_token : str
An API token for `AQT ARNICA <https://www.aqt.eu/products/arnica/>`_.
device_instance : str
The device instance of the AQT backend such as "ibex" or "simulator_noise".
For an up-to-date list, see the AQT ARNICA website.
workspace : str
The workspace for a company or project.
Examples
--------
We evaluate a :ref:`QuantumFloat` multiplication on the 12-qubit AQT IBEX.
>>> from qrisp import QuantumFloat
>>> from qrisp.interface import AQTBackend
>>> # qrisp_ibex = AQTBackend(api_token = "YOUR_AQT_ARNICA_TOKEN", device_instance = "simulator_noise", workspace = "aqt_simulators")
>>> qrisp_ibex = AQTBackend(api_token = "YOUR_AQT_ARNICA_TOKEN", device_instance = "ibex", workspace = "YOUR_COMPANY_OR_PROJECT_NAME")
>>> a = QuantumFloat(2)
>>> a[:] = 2
>>> b = a*a
>>> b.get_measurement(backend = qrisp_ibex, shots = 100)
{4: 0.49,
8: 0.11,
2: 0.08,
0: 0.06,
14: 0.06,
5: 0.04,
12: 0.04,
13: 0.03,
3: 0.02,
10: 0.02,
15: 0.02,
6: 0.01,
7: 0.01,
11: 0.01}
"""
def __init__(self, api_token, device_instance, workspace):
if not isinstance(api_token, str):
raise TypeError(
"api_token must be a string. You can create an API token on the AQT ARNICA website."
)
if not isinstance(workspace, str):
raise TypeError(
"workspace must be a string."
)
if not isinstance(device_instance, str):
raise TypeError(
"Please provide a device_instance as a string. You can retrieve a list of available devices on the AQT ARNICA website."
)
try:
from qiskit_aqt_provider import AQTProvider
from qiskit_aqt_provider.primitives import AQTSampler
except ImportError:
raise ImportError(
"Please install qiskit-aqt-provider to use the AQTBackend. You can do this by running `pip install qiskit-aqt-provider`."
)
provider = AQTProvider(api_token)
backend = provider.get_backend(name = device_instance, workspace = workspace)
"""
def run(qasm_str, shots=None, token=""):
if shots is None:
shots = 100
# Convert to qiskit
qiskit_qc = qiskit.QuantumCircuit.from_qasm_str(qasm_str)
# Make circuit with one monolithic register
new_qiskit_qc = qiskit.QuantumCircuit(len(qiskit_qc.qubits), len(qiskit_qc.clbits))
for instr in qiskit_qc:
new_qiskit_qc.append(
instr.operation,
[qiskit_qc.qubits.index(qb) for qb in instr.qubits],
[qiskit_qc.clbits.index(cb) for cb in instr.clbits],
)
# Instantiate a sampler on the execution backend.
sampler = AQTSampler(backend)
# Optional: set the transpiler's optimization level.
# Optimization level 3 typically provides the best results.
sampler.set_transpile_options(optimization_level=3)
# Sample the circuit on the execution backend.
result = sampler.run(new_qiskit_qc, shots=shots).result()
quasi_dist = result.quasi_dists[0]
# Format to fit the qrisp result format
result_dic = {}
for item in list(quasi_dist.keys()):
new_key = bin(item)[2:].zfill(len(qiskit_qc.clbits))
result_dic.setdefault(new_key, quasi_dist[item])
return result_dic
"""
def run_batch_aqt(batch):
circuit_batch = []
shot_batch = []
cl_bits_batch = []
for qc, shots in batch:
# Sometimes wrong results without transpilation
qiskit_qc = qc.transpile().to_qiskit()
# Make circuit with one monolithic register
new_qiskit_qc = qiskit.QuantumCircuit(len(qiskit_qc.qubits), len(qiskit_qc.clbits))
for instr in qiskit_qc:
new_qiskit_qc.append(
instr.operation,
[qiskit_qc.qubits.index(qb) for qb in instr.qubits],
[qiskit_qc.clbits.index(cb) for cb in instr.clbits],
)
circuit_batch.append(new_qiskit_qc)
cl_bits_batch.append(len(qiskit_qc.clbits))
if shots is None:
shots = 100
shot_batch.append(shots)
# Instantiate a sampler on the execution backend.
sampler = AQTSampler(backend)
# Optional: set the transpiler's optimization level.
# Optimization level 3 typically provides the best results.
sampler.set_transpile_options(optimization_level=3)
# Sample the circuit on the execution backend.
results = sampler.run(circuit_batch, shots = max(shot_batch)).result()
quasi_dist_batch = []
for i in range(len(batch)):
quasi_dist = results.quasi_dists[i]
cl_bits = cl_bits_batch[i]
new_quasi_dist = {}
for item in list(quasi_dist.keys()):
new_key = bin(item)[2:].zfill(cl_bits)
new_quasi_dist.setdefault(new_key, quasi_dist[item])
quasi_dist_batch.append(new_quasi_dist)
return quasi_dist_batch
# Call BatchedBackend constructor
if isinstance(backend.name, str):
name = backend.name
else:
name = backend.name()
super().__init__(run_batch_aqt)