Source code for qibocal.protocols.randomized_benchmarking.dict_utils

import json
import pathlib

import numpy as np
from qibo import gates
from qibo.models import Circuit

SINGLE_QUBIT_CLIFFORDS_NAMES = {
    # Virtual gates
    "": gates.I,
    "minusX,minusY": lambda q: gates.U3(q, 0, np.pi / 2, np.pi / 2),  # Z
    "sqrtX,sqrtMinusY,sqrtMinusX": lambda q: gates.U3(
        q, 0, -np.pi / 2, 0
    ),  # gates.RZ(q, np.pi / 2)
    "sqrtX,sqrtY,sqrtMinusX": lambda q: gates.U3(
        q, 0, np.pi / 2, 0
    ),  # gates.RZ(q, -np.pi / 2)
    # pi rotations
    "minusX": lambda q: gates.U3(q, np.pi, -np.pi, 0),  # X
    "minusY": lambda q: gates.U3(q, np.pi, 0, 0),  # Y
    # pi/2 rotations
    "sqrtX": lambda q: gates.U3(q, np.pi / 2, -np.pi / 2, np.pi / 2),  # Rx(pi/2)
    "sqrtMinusX": lambda q: gates.U3(q, -np.pi / 2, -np.pi / 2, np.pi / 2),  # Rx(-pi/2)
    "sqrtY": lambda q: gates.U3(q, np.pi / 2, 0, 0),  # Ry(pi/2)
    "sqrtMinusY": lambda q: gates.U3(q, -np.pi / 2, 0, 0),  # Ry(-pi/2)
    # 2pi/3 rotations
    "sqrtX,sqrtY": lambda q: gates.U3(q, np.pi / 2, -np.pi / 2, 0),  # Rx(pi/2)Ry(pi/2)
    "sqrtX,sqrtMinusY": lambda q: gates.U3(
        q, np.pi / 2, -np.pi / 2, np.pi
    ),  # Rx(pi/2)Ry(-pi/2)
    "sqrtMinusX,sqrtY": lambda q: gates.U3(
        q, np.pi / 2, np.pi / 2, 0
    ),  # Rx(-pi/2)Ry(pi/2)
    "sqrtMinusX,sqrtMinusY": lambda q: gates.U3(
        q, np.pi / 2, np.pi / 2, -np.pi
    ),  # Rx(-pi/2)Ry(-pi/2)
    "sqrtY,sqrtX": lambda q: gates.U3(q, np.pi / 2, 0, np.pi / 2),  # Ry(pi/2)Rx(pi/2)
    "sqrtY,sqrtMinusX": lambda q: gates.U3(
        q, np.pi / 2, 0, -np.pi / 2
    ),  # Ry(pi/2)Rx(-pi/2)
    "sqrtMinusY,sqrtX": lambda q: gates.U3(
        q, np.pi / 2, -np.pi, np.pi / 2
    ),  # Ry(-pi/2)Rx(pi/2)
    "sqrtMinusY,sqrtMinusX": lambda q: gates.U3(
        q, np.pi / 2, np.pi, -np.pi / 2
    ),  # Ry(-pi/2)Rx(-pi/2)
    # Hadamard-like
    "minusX,sqrtY": lambda q: gates.U3(q, np.pi / 2, -np.pi, 0),  # X Ry(pi/2)
    "minusX,sqrtMinusY": lambda q: gates.U3(q, np.pi / 2, 0, np.pi),  # X Ry(-pi/2)
    "minusY,sqrtX": lambda q: gates.U3(
        q, np.pi / 2, np.pi / 2, np.pi / 2
    ),  # Y Rx(pi/2)
    "minusY,sqrtMinusX": lambda q: gates.U3(
        q, np.pi / 2, -np.pi / 2, -np.pi / 2
    ),  # Y Rx(-pi/2)
    "sqrtX,sqrtY,sqrtX": lambda q: gates.U3(
        q, np.pi, -np.pi / 4, np.pi / 4
    ),  # Rx(pi/2)Ry(pi/2)Rx(pi/2)
    "sqrtX,sqrtMinusY,sqrtX": lambda q: gates.U3(
        q, np.pi, np.pi / 4, -np.pi / 4
    ),  # Rx(-pi/2)Ry(pi/2)Rx(-pi/2)
}


# TODO: Expand when more entangling gates are calibrated
[docs] def find_cliffords(cz_list): """Splits a clifford (list of gates) into sublists based on the occurrence of the "CZ" gate.""" clifford_list = [] clifford = [] for gate in cz_list: if gate == "CZ": clifford.append(gate) clifford_list.append(clifford) clifford = [] continue clifford.append(gate) clifford_list.append(clifford) return clifford_list
[docs] def separator(clifford): """ Separates values in the given clifford sublist based on certain conditions. Returns: tuple: A tuple containing three elements: - values_with_1 (str): A comma-separated string of values containing '1'. - values_with_2 (str): A comma-separated string of values containing '2'. - value_with_CZ (bool): True if 'CZ' is present in the clifford list, False otherwise. """ # Separate values containing 1 values_with_1 = [value for value in clifford if "1" in value] values_with_1 = ",".join(values_with_1) # Separate values containing 2 values_with_2 = [value for value in clifford if "2" in value] values_with_2 = ",".join(values_with_2) # Check if CZ value_with_CZ = [value for value in clifford if "CZ" in value] value_with_CZ = len(value_with_CZ) == 1 # FIXME: What is this ? values_with_1 = values_with_1.replace("1", "") values_with_2 = values_with_2.replace("2", "") return values_with_1, values_with_2, value_with_CZ
[docs] def clifford2gates(clifford): """ Converts a Clifford string into a list of gates. Args: clifford (str): A comma-separated string representing a sequence of gates that represent a Clifford gate. """ gate_list = clifford.split(",") clifford_list = find_cliffords(gate_list) clifford_gate = [] for clifford in clifford_list: values_with_1, values_with_2, value_with_CZ = separator(clifford) clifford_gate.append(SINGLE_QUBIT_CLIFFORDS_NAMES[values_with_1](0)) clifford_gate.append(SINGLE_QUBIT_CLIFFORDS_NAMES[values_with_2](1)) if value_with_CZ: clifford_gate.append(gates.CZ(0, 1)) return clifford_gate
[docs] def clifford_to_matrix(clifford): """ Converts a Clifford gate as a string to its corresponding unitary matrix representation. """ clifford_gate = clifford2gates(clifford) qubits_str = ["q0", "q1"] new_circuit = Circuit(2, wire_names=qubits_str) for gate in clifford_gate: new_circuit.add(gate) unitary = new_circuit.unitary() return unitary
[docs] def generate_inv_dict_cliffords_file(two_qubit_cliffords, output_file=None): """ Generate an inverse dictionary of Clifford matrices and save it to a npz file. Parameters: two_qubit_cliffords (dict): A dictionary of two-qubit Cliffords. output_file (str): The path to the output npz file. """ clifford_matrices = {} for i, clifford in enumerate(two_qubit_cliffords.values()): clifford = two_qubit_cliffords[str(i)] unitary = clifford_to_matrix(clifford) unitary = unitary.round(3) unitary += 0.0 + 0.0j clifford_matrices[i] = unitary clifford_matrices_inv_np = {} # Convert the arrays to strings and store them as keys in the new dictionary for key, value in clifford_matrices.items(): key_str = np.array2string(value, separator=",") clifford_matrices_inv_np[key_str] = key if output_file is not None: np.savez(output_file, **clifford_matrices_inv_np) return clifford_matrices_inv_np
[docs] def clifford_to_pulses(clifford): """ From a Clifford gate sequence into the number of pulses required to implement it. Args: clifford (str): A comma-separated string representing the Clifford gate sequence. Returns: int: The number of pulses required to implement the given Clifford gate sequence. """ gate_list = clifford.split(",") clifford_list = find_cliffords(gate_list) pulses = 0 for clifford in clifford_list: values_with_1, values_with_2, value_with_CZ = separator(clifford) if SINGLE_QUBIT_CLIFFORDS_NAMES[values_with_1](0).name != "id": pulses += 2 # This assumes a U3 transpiled into 2 pulses if SINGLE_QUBIT_CLIFFORDS_NAMES[values_with_2](1).name != "id": pulses += 2 # This assumes a U3 transpiled into 2 pulses if value_with_CZ: pulses += 1 # This assumes a CZ without parking so 1 pulse return pulses
[docs] def calculate_pulses_clifford(cliffords): """ Calculate the average number of pulses per Clifford operation. Parameters: - cliffords (dict): A dictionary of Clifford operations. Returns: - pulses_per_clifford (float): The average number of pulses per Clifford operation. """ pulses = 0 for i, clifford in enumerate(cliffords.values()): clifford = cliffords[str(i)] pulses += clifford_to_pulses(clifford) pulses_per_clifford = pulses / len(cliffords) return pulses_per_clifford
[docs] def load_inverse_cliffords(file_inv): path = pathlib.Path(__file__).parent / file_inv clifford_matrices_inv = np.load(path) return clifford_matrices_inv
[docs] def load_cliffords(file_cliffords): path = pathlib.Path(__file__).parent / file_cliffords with open(path) as file: two_qubit_cliffords = json.load(file) return two_qubit_cliffords