Skip to content

Commit

Permalink
Merge pull request #780 from qiboteam/save_RB_ditckeys
Browse files Browse the repository at this point in the history
Save random_indexes for the RB
  • Loading branch information
andrea-pasquale authored Apr 4, 2024
2 parents 482e8ea + c8e4aaf commit 6b7b694
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from qibo.models import Circuit


def layer_circuit(layer_gen: Callable, depth: int, qubit, seed) -> Circuit:
"""Creates a circuit of `depth` layers from a generator `layer_gen` yielding `Circuit` or `Gate`.
def layer_circuit(rb_gen: Callable, depth: int, qubit) -> tuple[Circuit, dict]:
"""Creates a circuit of `depth` layers from a generator `layer_gen` yielding `Circuit` or `Gate`
and a dictionary with random indexes used to select the clifford gates.
Args:
layer_gen (Callable): Should return gates or a full circuit specifying a layer.
Expand All @@ -20,19 +21,25 @@ def layer_circuit(layer_gen: Callable, depth: int, qubit, seed) -> Circuit:
"""

full_circuit = None
random_indexes = []
# Build each layer, there will be depth many in the final circuit.
qubits_str = [str(qubit)]

for _ in range(depth):
# Generate a layer.
new_layer = layer_gen(1, seed) # TODO: find better implementation
new_layer, random_index = rb_gen.layer_gen()
# Ensure new_layer is a circuit
if isinstance(new_layer, Gate):
new_circuit = Circuit(1, wire_names=qubits_str)
new_circuit.add(new_layer)
random_indexes.append(random_index)

# We are only using this for the RB we have right now
elif all(isinstance(gate, Gate) for gate in new_layer):
new_circuit = Circuit(1, wire_names=qubits_str)

new_circuit.add(new_layer)
random_indexes.append(random_index)

elif isinstance(new_layer, Circuit):
new_circuit = new_layer
else:
Expand All @@ -43,7 +50,7 @@ def layer_circuit(layer_gen: Callable, depth: int, qubit, seed) -> Circuit:
if full_circuit is None: # instantiate in first loop
full_circuit = Circuit(new_circuit.nqubits, wire_names=qubits_str)
full_circuit = full_circuit + new_circuit
return full_circuit
return full_circuit, random_indexes


def add_inverse_layer(circuit: Circuit, single_qubit=True):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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 qibo.models import Circuit
from qibolab.platform import Platform
from qibolab.qubits import QubitId

Expand Down Expand Up @@ -84,8 +84,8 @@ class RBData(Data):
"""Number of iterations for each depth."""
data: dict[QubitId, npt.NDArray[RBType]] = field(default_factory=dict)
"""Raw data acquired."""
circuits: Circuit = None
"""Circuits executed."""
circuits: dict[QubitId, list[list[int]]] = field(default_factory=dict)
"""Clifford gate indexes executed."""

def extract_probabilities(self, qubit):
"""Extract the probabilities given `qubit`"""
Expand Down Expand Up @@ -117,16 +117,44 @@ def __contains__(self, qubit: QubitId):
return True


def layer_gen(targets, seed):
"""Returns a circuit with a random single-qubit clifford unitary."""
return random_clifford(targets, seed)
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,
seed,
rb_gen,
noise_model=None,
) -> Iterable:
"""Returns single-qubit random self-inverting Clifford circuits.
Expand All @@ -143,16 +171,18 @@ def random_circuits(
"""

circuits = []
indexes = defaultdict(list)
for _ in range(niter):
for target in targets:
circuit = layer_circuit(layer_gen, depth, target, seed)
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
return circuits, indexes


def _acquisition(
Expand Down Expand Up @@ -198,14 +228,18 @@ def _acquisition(
)

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_circuits(
depth, qubits_ids, params.niter, params.seed, noise_model
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]
# Execute the circuits
if params.unrolling:
executed_circuits = backend.execute_circuits(circuits, nshots=params.nshots)
Expand All @@ -218,6 +252,7 @@ def _acquisition(
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):
Expand All @@ -228,6 +263,7 @@ def _acquisition(
samples=samples[index[0] : index[1]][:, nqubit],
),
)
data.circuits = indexes

return data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import numpy as np
from qibo import gates

from qibocal.config import raise_error
from qibocal.protocols.characterization.utils import significant_digit

SINGLE_QUBIT_CLIFFORDS = {
Expand Down Expand Up @@ -42,8 +41,8 @@
}


def random_clifford(qubits, seed=None):
"""Generates random Clifford operator(s).
def random_clifford(random_index_gen):
"""Generates random Clifford operator.
Args:
qubits (int or list or ndarray): if ``int``, the number of qubits for the Clifford.
Expand All @@ -56,34 +55,10 @@ def random_clifford(qubits, seed=None):
(list of :class:`qibo.gates.Gate`): Random Clifford operator(s).
"""

if (
not isinstance(qubits, int)
and not isinstance(qubits, list)
and not isinstance(qubits, np.ndarray)
):
raise_error(
TypeError,
f"qubits must be either type int, list or ndarray, but it is type {type(qubits)}.",
)
if isinstance(qubits, int) and qubits <= 0:
raise_error(ValueError, "qubits must be a positive integer.")

if isinstance(qubits, (list, np.ndarray)) and any(q < 0 for q in qubits):
raise_error(ValueError, "qubit indexes must be non-negative integers.")

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

if isinstance(qubits, int):
qubits = list(range(qubits))

random_indexes = local_state.integers(0, len(SINGLE_QUBIT_CLIFFORDS), len(qubits))
clifford_gates = [
SINGLE_QUBIT_CLIFFORDS[p](q) for p, q in zip(random_indexes, qubits)
]
random_index = int(random_index_gen(SINGLE_QUBIT_CLIFFORDS))
clifford_gate = SINGLE_QUBIT_CLIFFORDS[random_index](0)

return clifford_gates
return clifford_gate, random_index


def number_to_str(
Expand Down
24 changes: 13 additions & 11 deletions tests/test_randomized_benchmarking.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
fitting,
noisemodels,
)
from qibocal.protocols.characterization.randomized_benchmarking.standard_rb import (
RB_Generator,
)
from qibocal.protocols.characterization.randomized_benchmarking.utils import (
number_to_str,
random_clifford,
Expand Down Expand Up @@ -143,15 +146,8 @@ def test_model(noise_model, num_keys=1):
@pytest.mark.parametrize("seed", [10])
@pytest.mark.parametrize("qubits", [1, 2, [0, 1], np.array([0, 1])])
def test_random_clifford(qubits, seed):
with pytest.raises(TypeError):
q = "1"
random_clifford(q)
with pytest.raises(ValueError):
q = -1
random_clifford(q)
with pytest.raises(ValueError):
q = [0, 1, -3]
random_clifford(q)

rb_gen = RB_Generator(seed)

result_single = np.array([[1j, -1j], [-1j, -1j]]) / np.sqrt(2)

Expand All @@ -164,9 +160,15 @@ def test_random_clifford(qubits, seed):
]
)

result = result_single if (isinstance(qubits, int) and qubits == 1) else result_two
result = result_single if isinstance(qubits, int) else result_two

if isinstance(qubits, int):
qubits = [qubits]
gates = []
for qubit in qubits:
gate, index = random_clifford(rb_gen.random_index)
gates.append(gate)

gates = random_clifford(qubits, seed=seed)
matrix = reduce(np.kron, [gate.matrix() for gate in gates])
assert np.allclose(matrix, result)

Expand Down

0 comments on commit 6b7b694

Please sign in to comment.