Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling global backend for protocols involving circuits #1076

Merged
merged 12 commits into from
Feb 12, 2025
29 changes: 13 additions & 16 deletions src/qibocal/auto/transpile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Optional

from qibo import Circuit
from qibo.backends.abstract import Backend
from qibo.backends import Backend
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.unroller import NativeGates, Unroller
from qibolab.qubits import QubitId
Expand All @@ -28,20 +28,18 @@ def transpile_circuits(
are all string or all integers.
"""
transpiled_circuits = []

qubits = list(backend.platform.qubits)
platform = backend.platform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle here you could add something like

from qibolab.platform import Platform

if not isinstance(platform, Platform):
    transpiled_circuits = circuits

to retain the previous behavior when the backend is not qibolab.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep assuming it is Qibolab, to make everything simpler. Even though in some places may be simpler to deal with other backends, it may fail in other places. We will overhaul this consistently later on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the expected behavior would be to have a qibolab platform

if platform is None:
raise ValueError("Qibocal requires a Qibolab platform to run.")

qubits = list(platform.qubits)
if isinstance(qubit_maps[0][0], str):
for i, qubit_map in enumerate(qubit_maps):
qubit_map = map(lambda x: qubits.index(x), qubit_map)
qubit_maps[i] = list(qubit_map)
if backend.name == "qibolab":
platform_nqubits = backend.platform.nqubits
for circuit, qubit_map in zip(circuits, qubit_maps):
new_circuit = pad_circuit(platform_nqubits, circuit, qubit_map)
transpiled_circ, _ = transpiler(new_circuit)
transpiled_circuits.append(transpiled_circ)
else:
transpiled_circuits = circuits
platform_nqubits = platform.nqubits
for circuit, qubit_map in zip(circuits, qubit_maps):
new_circuit = pad_circuit(platform_nqubits, circuit, qubit_map)
transpiled_circ, _ = transpiler(new_circuit)
transpiled_circuits.append(transpiled_circ)

return transpiled_circuits


Expand Down Expand Up @@ -99,20 +97,19 @@ def execute_transpiled_circuit(
backend,
transpiler,
)[0]

return transpiled_circ, backend.execute_circuit(
transpiled_circ, initial_state=initial_state, nshots=nshots
)


def dummy_transpiler(backend) -> Optional[Passes]:
def dummy_transpiler(backend: Backend) -> Passes:
"""
If the backend is `qibolab`, a transpiler with just an unroller is returned,
otherwise None.
"""
if backend.name == "qibolab":
unroller = Unroller(NativeGates.default())
return Passes(connectivity=backend.platform.topology, passes=[unroller])
return None
unroller = Unroller(NativeGates.default())
return Passes(connectivity=backend.platform.topology, passes=[unroller])


def pad_circuit(nqubits, circuit: Circuit, qubit_map: list[int]) -> Circuit:
Expand Down
52 changes: 0 additions & 52 deletions src/qibocal/protocols/randomized_benchmarking/noisemodels.py

This file was deleted.

8 changes: 1 addition & 7 deletions src/qibocal/protocols/randomized_benchmarking/standard_rb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Iterable, Optional, TypedDict, Union

import numpy as np
Expand Down Expand Up @@ -52,12 +52,6 @@ class StandardRBParameters(Parameters):
If ``None``, uses a random seed.
Defaults is ``None``.
"""
noise_model: Optional[str] = None
"""For simulation purposes, string has to match what is in
:mod:`qibocal.protocols.randomized_benchmarking.noisemodels`"""
noise_params: Optional[list] = field(default_factory=list)
"""With this the noise model will be initialized, if not given random
values will be used."""
nshots: int = 10
"""Just to add the default value."""

Expand Down
46 changes: 12 additions & 34 deletions src/qibocal/protocols/randomized_benchmarking/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import numpy as np
import numpy.typing as npt
from qibo import gates
from qibo.backends import get_backend
from qibo.config import raise_error
from qibo.backends import construct_backend
from qibo.models import Circuit
from qibolab.platform import Platform
from qibolab.qubits import QubitId, QubitPairId
Expand All @@ -19,8 +18,6 @@
execute_transpiled_circuit,
execute_transpiled_circuits,
)
from qibocal.config import raise_error
from qibocal.protocols.randomized_benchmarking import noisemodels
from qibocal.protocols.randomized_benchmarking.dict_utils import (
SINGLE_QUBIT_CLIFFORDS_NAMES,
calculate_pulses_clifford,
Expand Down Expand Up @@ -118,7 +115,6 @@ def random_circuits(
targets: list[Union[QubitId, QubitPairId]],
niter,
rb_gen,
noise_model=None,
inverse_layer=True,
single_qubit=True,
file_inv=pathlib.Path(),
Expand All @@ -134,8 +130,6 @@ def random_circuits(
if inverse_layer:
add_inverse_layer(circuit, rb_gen, single_qubit, file_inv)
add_measurement_layer(circuit)
if noise_model is not None:
circuit = noise_model.apply(circuit)
circuits.append(circuit)
indexes[target].append(random_index)

Expand Down Expand Up @@ -341,30 +335,17 @@ def setup(
interleave: Optional[str] = None,
):
"""
Set up the randomized benchmarking experiment backend, noise model and data class.
Set up the randomized benchmarking experiment backend and data class.

Args:
params (Parameters): The parameters for the experiment.
single_qubit (bool, optional): Flag indicating whether the experiment is for a single qubit or two qubits. Defaults to True.
interleave: (str, optional): The type of interleaving to apply. Defaults to None.

Returns:
tuple: A tuple containing the experiment data, noise model, and backend.
tuple: A tuple containing the experiment data and backend.
"""

backend = get_backend()
backend.platform = platform
# For simulations, a noise model can be added.
noise_model = None
if params.noise_model is not None:
if backend.name == "qibolab":
raise_error(
ValueError,
"Backend qibolab (%s) does not perform noise models simulation. ",
)

noise_model = getattr(noisemodels, params.noise_model)(params.noise_params)
params.noise_params = noise_model.params.tolist()
backend = construct_backend(backend="qibolab", platform=platform)
# Set up the scan (here an iterator of circuits of random clifford gates with an inverse).
if single_qubit:
cls = RBData
Expand All @@ -380,12 +361,10 @@ def setup(
niter=params.niter,
)

return data, noise_model, backend
return data, backend


def get_circuits(
params, targets, add_inverse_layer, interleave, noise_model, single_qubit=True
):
def get_circuits(params, targets, add_inverse_layer, interleave, single_qubit=True):
"""
Generate randomized benchmarking circuits.

Expand All @@ -394,7 +373,6 @@ def get_circuits(
targets (list): List of target qubit IDs.
add_inverse_layer (bool): Flag indicating whether to add an inverse layer to the circuits.
interleave (str): String indicating whether to interleave the circuits with the given gate.
noise_model (str): Noise model string.
single_qubit (bool, optional): Flag indicating whether to generate single qubit circuits.

Returns:
Expand All @@ -421,7 +399,6 @@ def get_circuits(
qubits_ids,
params.niter,
rb_gen,
noise_model,
add_inverse_layer,
single_qubit,
inv_file,
Expand Down Expand Up @@ -455,6 +432,7 @@ def execute_circuits(circuits, targets, params, backend, single_qubit=True):

"""
# Execute the circuits
platform = backend.platform
transpiler = dummy_transpiler(backend)
qubit_maps = (
[[i] for i in targets] * (len(params.depths) * params.niter)
Expand Down Expand Up @@ -503,9 +481,9 @@ def rb_acquisition(
Returns:
RBData: The depths, samples, and ground state probability of each experiment in the scan.
"""
data, noise_model, backend = setup(params, platform, single_qubit=True)
data, backend = setup(params, platform, single_qubit=True)
circuits, indexes, npulses_per_clifford = get_circuits(
params, targets, add_inverse_layer, interleave, noise_model, single_qubit=True
params, targets, add_inverse_layer, interleave, single_qubit=True
)
executed_circuits = execute_circuits(circuits, targets, params, backend)

Expand Down Expand Up @@ -549,10 +527,10 @@ def twoq_rb_acquisition(
Returns:
RB2QData: The acquired data for two qubit randomized benchmarking.
"""

data, noise_model, backend = setup(params, platform, single_qubit=False)
targets = [tuple(pair) if isinstance(pair, list) else pair for pair in targets]
data, backend = setup(params, platform, single_qubit=False)
circuits, indexes, npulses_per_clifford = get_circuits(
params, targets, add_inverse_layer, interleave, noise_model, single_qubit=False
params, targets, add_inverse_layer, interleave, single_qubit=False
)
executed_circuits = execute_circuits(
circuits, targets, params, backend, single_qubit=False
Expand Down
48 changes: 26 additions & 22 deletions src/qibocal/protocols/readout_mitigation_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy.typing as npt
import plotly.express as px
from qibo import gates
from qibo.backends import get_backend
from qibo.backends import construct_backend
from qibo.models import Circuit
from qibolab.platform import Platform
from qibolab.qubits import QubitId
Expand Down Expand Up @@ -61,8 +61,7 @@ def _acquisition(
data = ReadoutMitigationMatrixData(
nshots=params.nshots, qubit_list=[list(qq) for qq in targets]
)
backend = get_backend()
backend.platform = platform
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)
qubit_map = [i for i in range(platform.nqubits)]
for qubits in targets:
Expand Down Expand Up @@ -125,25 +124,30 @@ def _plot(
fitting_report = ""
figs = []
if fit is not None:
computational_basis = [
format(i, f"0{len(target)}b") for i in range(2 ** len(target))
]
measurement_matrix = np.linalg.inv(fit.readout_mitigation_matrix[tuple(target)])
z = measurement_matrix
fig = px.imshow(
z,
x=computational_basis,
y=computational_basis,
text_auto=True,
labels={
"x": "Prepeared States",
"y": "Measured States",
"color": "Probabilities",
},
width=700,
height=700,
)
figs.append(fig)
if tuple(target) in fit.readout_mitigation_matrix:
computational_basis = [
format(i, f"0{len(target)}b") for i in range(2 ** len(target))
]
# use pinv since it should be already invertibile
# however when casting to list we could lose precision
measurement_matrix = np.linalg.pinv(
fit.readout_mitigation_matrix[tuple(target)]
)
z = measurement_matrix
fig = px.imshow(
z,
x=computational_basis,
y=computational_basis,
text_auto=True,
labels={
"x": "Prepeared States",
"y": "Measured States",
"color": "Probabilities",
},
width=700,
height=700,
)
figs.append(fig)
return figs, fitting_report


Expand Down
5 changes: 2 additions & 3 deletions src/qibocal/protocols/state_tomography.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibo import Circuit, gates
from qibo.backends import NumpyBackend, get_backend, matrices
from qibo.backends import NumpyBackend, construct_backend, matrices
from qibo.quantum_info import fidelity, partial_trace
from qibolab.platform import Platform
from qibolab.qubits import QubitId
Expand Down Expand Up @@ -102,8 +102,7 @@ def _acquisition(
if params.circuit is None:
params.circuit = Circuit(len(targets))

backend = get_backend()
backend.platform = platform
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)

data = StateTomographyData(
Expand Down
5 changes: 2 additions & 3 deletions src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibo.backends import get_backend
from qibo.backends import construct_backend
from qibolab import ExecutionParameters
from qibolab.platform import Platform
from qibolab.qubits import QubitId, QubitPairId
Expand Down Expand Up @@ -223,8 +223,7 @@ def _acquisition_circuits(
bell_states=params.bell_states,
thetas=thetas.tolist(),
)
backend = get_backend()
backend.platform = platform
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)
if params.apply_error_mitigation:
mitigation_data = mitigation_acquisition(
Expand Down
5 changes: 2 additions & 3 deletions src/qibocal/protocols/two_qubit_state_tomography.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibo import Circuit, gates
from qibo.backends import NumpyBackend, get_backend
from qibo.backends import NumpyBackend, construct_backend
from qibo.quantum_info import fidelity, partial_trace
from qibo.result import QuantumState
from qibolab.platform import Platform
Expand Down Expand Up @@ -97,9 +97,8 @@ def _acquisition(
if params.circuit is None:
params.circuit = Circuit(len(qubits))

backend = get_backend()
backend.platform = platform
simulator = NumpyBackend()
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)

simulated_state = simulator.execute_circuit(deepcopy(params.circuit))
Expand Down
Loading