Skip to content

Commit

Permalink
Merge pull request #834 from qiboteam/filtered_rb2
Browse files Browse the repository at this point in the history
feature: implement filtered rb
  • Loading branch information
Edoardo-Pedicillo authored May 16, 2024
2 parents f869f28 + cba8f03 commit 6633379
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 199 deletions.
4 changes: 3 additions & 1 deletion src/qibocal/protocols/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from .rabi.length_signal import rabi_length_signal
from .ramsey.ramsey import ramsey
from .ramsey.ramsey_signal import ramsey_signal
from .randomized_benchmarking.filtered_rb import filtered_rb
from .randomized_benchmarking.standard_rb import standard_rb
from .readout_characterization import readout_characterization
from .readout_mitigation_matrix import readout_mitigation_matrix
Expand Down Expand Up @@ -102,12 +103,13 @@ class Operation(Enum):
chevron_signal = chevron_signal
cz_virtualz = cz_virtualz
standard_rb = standard_rb
readout_characterization = readout_characterization
filtered_rb = filtered_rb
resonator_frequency = resonator_frequency
fast_reset = fast_reset
zeno = zeno
zeno_signal = zeno_signal
chsh_pulses = chsh_pulses
readout_characterization = readout_characterization
chsh_circuits = chsh_circuits
readout_mitigation_matrix = readout_mitigation_matrix
twpa_frequency = twpa_frequency
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from dataclasses import dataclass

import numpy as np
import plotly.graph_objects as go
from qibolab.platform import Platform
from qibolab.qubits import QubitId

from qibocal.auto.operation import Results, Routine
from qibocal.protocols.characterization.randomized_benchmarking.utils import (
rb_acquisition,
)
from qibocal.protocols.characterization.utils import table_dict, table_html

from .standard_rb import RBData, StandardRBParameters


@dataclass
class FilteredRBParameters(StandardRBParameters):
"""Filtered Randomized Benchmarking runcard inputs."""


@dataclass
class FilteredRBResult(Results):
"""Filtered RB outputs."""


def _acquisition(
params: FilteredRBParameters,
platform: Platform,
targets: list[QubitId],
) -> RBData:
"""The data acquisition stage of Filtered Randomized Benchmarking.
1. Set up the scan
2. Execute the scan
3. Post process the data and initialize a filtered rb data object with it.
Args:
params : All parameters in one object.
platform : Platform the experiment is executed on.
target : list of qubits the experiment is executed on.
Returns:
RBData: The depths, samples and ground state probability of each experiment in the scan.
"""

return rb_acquisition(params, targets, add_inverse_layer=False)


def _fit(data: RBData) -> FilteredRBResult:
"""Takes a data frame, extracts the depths and the signal and fits it with an
exponential function y = Ap^x+B.
Args:
data (RBData): Data from the data acquisition stage.
Returns:
FilteredRBResult: Aggregated and processed data.
"""
return FilteredRBResult()


def _plot(
data: RBData, fit: FilteredRBResult, target: QubitId
) -> tuple[list[go.Figure], str]:
"""Builds the table for the qq pipe, calls the plot function of the result object
and returns the figure es list.
Args:
data (RBData): Data object used for the table.
fit (FilteredRBResult): Is called for the plot.
target (_type_): Not used yet.
Returns:
tuple[list[go.Figure], str]:
"""

qubit = target
fig = go.Figure()
fitting_report = ""
x = data.depths
raw_data = data.extract_probabilities(qubit)
y = np.mean(raw_data, axis=1)
raw_depths = [[depth] * data.niter for depth in data.depths]

fig.add_trace(
go.Scatter(
x=np.hstack(raw_depths),
y=np.hstack(raw_data),
line=dict(color="#6597aa"),
mode="markers",
marker={"opacity": 0.2, "symbol": "square"},
name="iterations",
)
)

fig.add_trace(
go.Scatter(
x=x,
y=y,
line=dict(color="#aa6464"),
mode="markers",
name="average",
)
)

if fit is not None:
fitting_report = table_html(
table_dict(
qubit,
["niter", "nshots"],
[
data.niter,
data.nshots,
],
)
)

fig.update_layout(
showlegend=True,
xaxis_title="Circuit depth",
yaxis_title="Survival Probability",
)

return [fig], fitting_report


filtered_rb = Routine(_acquisition, _fit, _plot)
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Iterable, Optional, TypedDict, Union

import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibo.backends import GlobalBackend
from qibolab.platform import Platform
from qibolab.qubits import QubitId

from qibocal.auto.operation import Data, Parameters, Results, Routine
from qibocal.auto.transpile import (
dummy_transpiler,
execute_transpiled_circuit,
execute_transpiled_circuits,
)
from qibocal.config import raise_error
from qibocal.protocols.characterization.randomized_benchmarking import noisemodels
from qibocal.auto.operation import Parameters, Results, Routine

from ..utils import table_dict, table_html
from .circuit_tools import add_inverse_layer, add_measurement_layer, layer_circuit
from .fitting import exp1B_func, fit_exp1B_func
from .utils import data_uncertainties, number_to_str, random_clifford
from .utils import RBData, data_uncertainties, number_to_str, rb_acquisition

NPULSES_PER_CLIFFORD = 1.875

Expand Down Expand Up @@ -66,42 +55,6 @@ def __post_init__(self):
)


RBType = np.dtype(
[
("samples", np.int32),
]
)
"""Custom dtype for RB."""


@dataclass
class RBData(Data):
"""The output of the acquisition function."""

depths: list
"""Circuits depths."""
uncertainties: Optional[float]
"""Parameters uncertainties."""
seed: Optional[int]
nshots: int
"""Number of shots."""
niter: int
"""Number of iterations for each depth."""
data: dict[QubitId, npt.NDArray[RBType]] = field(default_factory=dict)
"""Raw data acquired."""
circuits: dict[QubitId, list[list[int]]] = field(default_factory=dict)
"""Clifford gate indexes executed."""

def extract_probabilities(self, qubit):
"""Extract the probabilities given `qubit`"""
probs = []
for depth in self.depths:
data_list = np.array(self.data[qubit, depth].tolist())
data_list = data_list.reshape((-1, self.nshots))
probs.append(np.count_nonzero(1 - data_list, axis=1) / data_list.shape[1])
return probs


@dataclass
class StandardRBResult(Results):
"""Standard RB outputs."""
Expand All @@ -122,74 +75,6 @@ def __contains__(self, qubit: QubitId):
return True


class RB_Generator:
"""
This class generates random single qubit cliffords for randomized benchmarking.
"""

def __init__(self, seed):
self.seed = seed
self.local_state = (
np.random.default_rng(seed)
if seed is None or isinstance(seed, int)
else seed
)

def random_index(self, gate_list):
"""
Generates a random index within the range of the given gate list.
Parameters:
- gate_list (list): Dict of gates.
Returns:
- int: Random index.
"""
return self.local_state.integers(0, len(gate_list), 1)

def layer_gen(self):
"""
Returns:
- Gate: Random single-qubit clifford .
"""
return random_clifford(self.random_index)


def random_circuits(
depth: int,
targets: list[QubitId],
niter,
rb_gen,
noise_model=None,
) -> Iterable:
"""Returns single-qubit random self-inverting Clifford circuits.
Args:
params (StandardRBParameters): Parameters of the RB protocol.
targets (list[QubitId]):
list of qubits the circuit is executed on.
nqubits (int, optional): Number of qubits of the resulting circuits.
If ``None``, sets ``len(qubits)``. Defaults to ``None``.
Returns:
Iterable: The iterator of circuits.
"""

circuits = []
indexes = defaultdict(list)
for _ in range(niter):
for target in targets:
circuit, random_index = layer_circuit(rb_gen, depth, target)
add_inverse_layer(circuit)
add_measurement_layer(circuit)
if noise_model is not None:
circuit = noise_model.apply(circuit)
circuits.append(circuit)
indexes[target].append(random_index)

return circuits, indexes


def _acquisition(
params: StandardRBParameters,
platform: Platform,
Expand All @@ -202,88 +87,15 @@ def _acquisition(
3. Post process the data and initialize a standard rb data object with it.
Args:
params (StandardRBParameters): All parameters in one object.
platform (Platform): Platform the experiment is executed on.
qubits (dict[int, Union[str, int]] or list[Union[str, int]]): list of qubits the experiment is executed on.
params: All parameters in one object.
platform: Platform the experiment is executed on.
target: list of qubits the experiment is executed on.
Returns:
RBData: The depths, samples and ground state probability of each experiment in the scan.
"""

backend = GlobalBackend()
# 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()
# 1. Set up the scan (here an iterator of circuits of random clifford gates with an inverse).
nqubits = len(targets)
data = RBData(
depths=params.depths,
uncertainties=params.uncertainties,
seed=params.seed,
nshots=params.nshots,
niter=params.niter,
)

circuits = []
indexes = {}
samples = []
qubits_ids = targets
rb_gen = RB_Generator(params.seed)
for depth in params.depths:
# TODO: This does not generate multi qubit circuits
circuits_depth, random_indexes = random_circuits(
depth, qubits_ids, params.niter, rb_gen, noise_model
)
circuits.extend(circuits_depth)
for qubit in random_indexes.keys():
indexes[(qubit, depth)] = random_indexes[qubit]
transpiler = dummy_transpiler(backend)
qubit_maps = [[i] for i in targets] * (len(params.depths) * params.niter)
# Execute the circuits
if params.unrolling:
_, executed_circuits = execute_transpiled_circuits(
circuits,
qubit_maps=qubit_maps,
backend=backend,
nshots=params.nshots,
transpiler=transpiler,
)
else:
executed_circuits = [
execute_transpiled_circuit(
circuit,
qubit_map=qubit_map,
backend=backend,
nshots=params.nshots,
transpiler=transpiler,
)[1]
for circuit, qubit_map in zip(circuits, qubit_maps)
]

for circ in executed_circuits:
samples.extend(circ.samples())
samples = np.reshape(samples, (-1, nqubits, params.nshots))
for i, depth in enumerate(params.depths):
index = (i * params.niter, (i + 1) * params.niter)
for nqubit, qubit_id in enumerate(targets):
data.register_qubit(
RBType,
(qubit_id, depth),
dict(
samples=samples[index[0] : index[1]][:, nqubit],
),
)
data.circuits = indexes

return data
return rb_acquisition(params, targets)


def _fit(data: RBData) -> StandardRBResult:
Expand Down
Loading

0 comments on commit 6633379

Please sign in to comment.