Source code for qiskit.ignis.verification.randomized_benchmarking.circuits

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# TODO(mtreinish): Remove these disables when implementation is finished
# pylint: disable=unused-argument,unnecessary-pass,invalid-name

"""
Generates randomized benchmarking sequences
"""

import copy
from typing import List, Optional
import numpy as np
import qiskit

from .Clifford import Clifford
from .clifford_utils import CliffordUtils as clutils
from .dihedral import CNOTDihedral
from .dihedral_utils import DihedralUtils as dutils


def handle_length_multiplier(length_multiplier, len_pattern,
                             is_purity=False):
    """
    Check validity of length_multiplier.
    In addition, transform it into a vector if it is a constant.
    In case of purity rb the length multiplier should be None.

    Args:
        length_multiplier (list): length of the multiplier
        len_pattern (int): length of the RB pattern
        is_purity (bool): True only for purity rb (default is False)

    Returns:
        list: length_multiplier

    Raises:
        ValueError: if the input is invalid
    """

    if hasattr(length_multiplier, "__len__"):
        if is_purity:
            raise ValueError(
                "In case of Purity RB the length multiplier should be None")
        if len(length_multiplier) != len_pattern:
            raise ValueError(
                "Length multiplier must be the same length as the pattern")
        length_multiplier = np.array(length_multiplier)
        if length_multiplier.dtype != 'int' or (length_multiplier < 1).any():
            raise ValueError("Invalid length multiplier")
    else:
        length_multiplier = np.ones(len_pattern, dtype='int')*length_multiplier

    return length_multiplier


def check_pattern(pattern, is_purity=False):
    """
    Verifies that the input pattern is valid
    i.e., that each qubit appears at most once

    In case of purity rb, checks that all simultaneous sequences have the same
    dimension (e.g. only 1-qubit sequences, or only 2-qubit sequences etc.)

    Args:
        pattern (list): RB pattern
        is_purity (bool): True only for purity rb (default is False)

    Raises:
        ValueError: if the pattern is not valid

    Return:
        tuple: of the form (``qlist``, ``maxqubit``, ``maxdim``) where:
            qlist: flat list of all the qubits in the pattern
            maxqubit: the maximum qubit number
            maxdim: the maximal dimension (maximal number of qubits
                in all sequences)
    """

    pattern_flat = []
    pattern_dim = []
    for pat in pattern:
        pattern_flat.extend(pat)
        pattern_dim.append(len(pat))

    _, uni_counts = np.unique(np.array(pattern_flat), return_counts=True)
    if (uni_counts > 1).any():
        raise ValueError("Invalid pattern. Duplicate qubit index.")

    dim_distinct = np.unique(pattern_dim)
    if is_purity:
        if len(dim_distinct) > 1:
            raise ValueError("Invalid pattern for purity RB. \
            All simultaneous sequences should have the \
            same dimension.")

    return pattern_flat, np.max(pattern_flat).item(), np.max(pattern_dim)


def calc_xdata(length_vector, length_multiplier):
    """
    Calculate the set of sequences lengths

    Args:
        length_vector (list): vector length
        length_multiplier (list): length of the multiplier of the vector length

    Returns:
        ndarray: An array of sequences lengths
    """

    xdata = []
    for mult in length_multiplier:
        xdata.append(np.array(length_vector)*mult)

    return np.array(xdata)


[docs]def randomized_benchmarking_seq(nseeds: int = 1, length_vector: Optional[List[int]] = None, rb_pattern: Optional[List[List[int]]] = None, length_multiplier: Optional[List[int]] = 1, seed_offset: int = 0, align_cliffs: bool = False, interleaved_gates: Optional[List[List[str]]] = None, is_purity: bool = False, group_gates: Optional[str] = None) -> \ (List[List[qiskit.QuantumCircuit]], List[List[int]], Optional[List[List[qiskit.QuantumCircuit]]], Optional[List[List[List[qiskit.QuantumCircuit]]]], Optional[int]): """Generate generic randomized benchmarking (RB) sequences. Args: nseeds: The number of seeds. For each seed the function generates a separate list of output RB circuits. length_vector: Length vector of the RB sequence lengths. Must be in ascending order. RB sequences of increasing length grow on top of the previous sequences. For example: * ``length_vector = [1, 10, 20, 50, 75, 100, 125, 150, 175]`` * ``length_vector = None`` is the same as ``length_vector = [1, 10, 20]`` rb_pattern: A list of the lists of integers representing the qubits indexes. For example, ``[[i,j],[k],...]`` will make simultaneous RB sequences, where there is a 2-qubit RB sequence on qbits Qi and Qj, and a 1-qubit RB sequence on qubit Qk, etc. Each qubit appers at most once. The number of qubits on which RB is done is the sum of the lists sizes. For example: * ``rb_pattern = [[0]]`` or ``rb_pattern = None`` -- \ create a 1-qubit RB sequence on qubit Q0. * ``rb_pattern = [[0,1]]`` -- \ create a 2-qubit RB sequence on qubits Q0 and Q1. * ``rb_pattern = [[2],[6,4]]`` -- \ create RB sequences that are 2-qubit RB for qubits Q6 and Q4, \ and 1-qubit RB for qubit Q2. length_multiplier: An array that scales each RB sequence by the multiplier. seed_offset: What to start the seeds at, if we want to add more seeds later. align_cliffs: If ``True`` adds a barrier across all qubits in the pattern after each set of group elements (not necessarily Cliffords). **Note:** the alignment considers the group multiplier. interleaved_gates: A list of lists of gates that will be interleaved. It is not ``None`` only for interleaved randomized benchmarking. The lengths of the lists should be equal to the length of the lists in ``rb_pattern``. is_purity: ``True`` only for purity randomized benchmarking (default is ``False``). **Note:** if ``is_purity = True`` then all patterns in ``rb_pattern`` should have the same dimension (e.g. only 1-qubit sequences, or only 2-qubit sequences), and ``length_multiplier = None``. group_gates: On which group (or set of gates) we perform RB (the default is the Clifford group). * ``group_gates='0'`` or ``group_gates=None`` or \ ``group_gates='Clifford'`` -- Clifford group. * ``group_gates='1'`` or ``group_gates='CNOT-Dihedral'`` \ or ``group_gates='Non-Clifford'`` -- CNOT-Dihedral group. Returns: A tuple of different fields depending on the inputs. The different fields are: * ``circuits``: list of lists of circuits for the RB sequences \ (a separate list for each seed). * ``xdata``: the sequences lengths (with multiplier if applicable). * ``circuits_interleaved``: \ (only if ``interleaved_gates`` is not ``None``): \ list of lists of circuits for the interleaved RB sequences \ (a separate list for each seed). * ``circuits_purity``: (only if ``is_purity=True``): \ list of lists of lists of circuits for purity RB \ (a separate list for each seed and each of the :math:`3^n` circuits). * ``npurity``: (only if ``is_purity=True``): \ the number of purity RB circuits (per seed) \ which equals to :math:`3^n`, where n is the dimension. Raises: ValueError: if ``group_gates`` is unknown. ValueError: if ``rb_pattern`` is not valid. ValueError: if ``length_multiplier`` is not valid. Examples: 1) Generate simultaneous standard RB sequences. .. code-block:: length_vector = [1,10,20] rb_pattern = [[0,3],[2],[1]] length_multiplier = [1,3,3] align_cliffs = True Create RB sequences that are 2-qubit RB for qubits Q0 and Q3, 1-qubit RB for qubit Q1, and 1-qubit RB for qubit Q2. Generate three times as many 1-qubit RB sequence elements, than 2-qubit elements. Place a barrier after 1 group element for the first pattern and after 3 group elements for the second and third patterns. The output ``xdata`` in this case is .. code-block:: xdata=[[1,10,20],[3,30,60],[3,30,60]] 2) Generate simultaneous interleaved RB sequences. .. code-block:: rb_pattern = [[0,3],[2],[1]] interleaved_gates = [['cx 0 1'], ['x 0'], ['h 0']] Interleave the 2-qubit gate ``cx`` on qubits Q0 and Q3, a 1-qubit gate ``x`` on qubit Q2, and a 1-qubit gate ``h`` on qubit Q1. 3) Generated purity RB sequences. .. code-block:: rb_pattern = [[0,3],[1,2]] npurity = True Create purity 2-qubit RB circuits separately on qubits Q0 and Q3 and on qubtis Q1 and Q2. The output is ``npurity = 9`` in this case. """ # Set modules (default is Clifford) if group_gates is None or group_gates in ('0', 'Clifford', 'clifford'): g_utils = clutils() g_group = Clifford rb_circ_type = 'rb' group_gates_type = 0 elif group_gates in ('1', 'Non-Clifford', 'NonClifford' 'CNOTDihedral', 'CNOT-Dihedral'): g_utils = dutils() g_group = CNOTDihedral rb_circ_type = 'rb_cnotdihedral' group_gates_type = 1 else: raise ValueError("Unknown group or set of gates.") if rb_pattern is None: rb_pattern = [[0]] if length_vector is None: length_vector = [1, 10, 20] qlist_flat, n_q_max, max_dim = check_pattern(rb_pattern, is_purity) length_multiplier = handle_length_multiplier(length_multiplier, len(rb_pattern), is_purity) # number of purity rb circuits per seed npurity = 3**max_dim xdata = calc_xdata(length_vector, length_multiplier) pattern_sizes = [len(pat) for pat in rb_pattern] max_nrb = np.max(pattern_sizes) # load group tables group_tables = [[] for _ in range(max_nrb)] for rb_num in range(max_nrb): group_tables[rb_num] = g_utils.load_tables(rb_num+1) # initialization: rb sequences circuits = [[] for e in range(nseeds)] # initialization: interleaved rb sequences circuits_interleaved = [[] for e in range(nseeds)] # initialization: non-clifford cnot-dihedral # rb sequences circuits_cnotdihedral = [[] for e in range(nseeds)] # initialization: non-clifford cnot-dihedral # interleaved rb sequences circuits_cnotdihedral_interleaved = [[] for e in range(nseeds)] # initialization: purity rb sequences circuits_purity = [[[] for d in range(npurity)] for e in range(nseeds)] # go through for each seed for seed in range(nseeds): qr = qiskit.QuantumRegister(n_q_max+1, 'qr') cr = qiskit.ClassicalRegister(len(qlist_flat), 'cr') general_circ = qiskit.QuantumCircuit(qr, cr) interleaved_circ = qiskit.QuantumCircuit(qr, cr) # make sequences for each of the separate sequences in # rb_pattern Elmnts = [] for rb_q_num in pattern_sizes: Elmnts.append(g_group(rb_q_num)) # Sequences for interleaved rb sequences Elmnts_interleaved = [] for rb_q_num in pattern_sizes: Elmnts_interleaved.append(g_group(rb_q_num)) # go through and add elements to RB sequences length_index = 0 for elmnts_index in range(length_vector[-1]): for (rb_pattern_index, rb_q_num) in enumerate(pattern_sizes): for _ in range(length_multiplier[rb_pattern_index]): new_elmnt_gatelist = g_utils.random_gates( rb_q_num) Elmnts[rb_pattern_index] = g_utils.compose_gates( Elmnts[rb_pattern_index], new_elmnt_gatelist) general_circ += replace_q_indices( get_quantum_circuit(g_utils.gatelist(), rb_q_num), rb_pattern[rb_pattern_index], qr) # add a barrier general_circ.barrier( *[qr[x] for x in rb_pattern[rb_pattern_index]]) # interleaved rb sequences if interleaved_gates is not None: Elmnts_interleaved[rb_pattern_index] = \ g_utils.compose_gates( Elmnts_interleaved[rb_pattern_index], new_elmnt_gatelist) interleaved_circ += replace_q_indices( get_quantum_circuit(g_utils.gatelist(), rb_q_num), rb_pattern[rb_pattern_index], qr) Elmnts_interleaved[rb_pattern_index] = \ g_utils.compose_gates( Elmnts_interleaved[rb_pattern_index], interleaved_gates[rb_pattern_index]) # add a barrier - interleaved rb interleaved_circ.barrier( *[qr[x] for x in rb_pattern[rb_pattern_index]]) interleaved_circ += replace_q_indices( get_quantum_circuit(g_utils.gatelist(), rb_q_num), rb_pattern[rb_pattern_index], qr) # add a barrier - interleaved rb interleaved_circ.barrier( *[qr[x] for x in rb_pattern[rb_pattern_index]]) if align_cliffs: # if align at a barrier across all patterns general_circ.barrier( *[qr[x] for x in qlist_flat]) # align for interleaved rb if interleaved_gates is not None: interleaved_circ.barrier( *[qr[x] for x in qlist_flat]) # if the number of elements matches one of the sequence lengths # then calculate the inverse and produce the circuit if (elmnts_index+1) == length_vector[length_index]: # circ for rb: circ = qiskit.QuantumCircuit(qr, cr) circ += general_circ # circ_interleaved for interleaved rb: circ_interleaved = qiskit.QuantumCircuit(qr, cr) circ_interleaved += interleaved_circ for (rb_pattern_index, rb_q_num) in enumerate(pattern_sizes): inv_key = g_utils.find_key(Elmnts[rb_pattern_index], rb_q_num) inv_circuit = g_utils.find_inverse_gates( rb_q_num, group_tables[rb_q_num-1][inv_key]) circ += replace_q_indices( get_quantum_circuit(inv_circuit, rb_q_num), rb_pattern[rb_pattern_index], qr) # calculate the inverse and produce the circuit # for interleaved rb if interleaved_gates is not None: inv_key = g_utils.find_key(Elmnts_interleaved [rb_pattern_index], rb_q_num) inv_circuit = g_utils.find_inverse_gates( rb_q_num, group_tables[rb_q_num - 1][inv_key]) circ_interleaved += replace_q_indices( get_quantum_circuit(inv_circuit, rb_q_num), rb_pattern[rb_pattern_index], qr) # Circuits for purity rb if is_purity: circ_purity = [[] for d in range(npurity)] for d in range(npurity): circ_purity[d] = qiskit.QuantumCircuit(qr, cr) circ_purity[d] += circ circ_purity[d].name = rb_circ_type + '_purity_' ind_d = d purity_qubit_num = 0 while True: # Per each qubit: # do nothing or rx(pi/2) or ry(pi/2) purity_qubit_rot = np.mod(ind_d, 3) ind_d = np.floor_divide(ind_d, 3) if purity_qubit_rot == 0: # do nothing circ_purity[d].name += 'Z' if purity_qubit_rot == 1: # add rx(pi/2) for pat in rb_pattern: circ_purity[d].rx(np.pi / 2, qr[pat[ purity_qubit_num]]) circ_purity[d].name += 'X' if purity_qubit_rot == 2: # add ry(pi/2) for pat in rb_pattern: circ_purity[d].ry(np.pi / 2, qr[pat[ purity_qubit_num]]) circ_purity[d].name += 'Y' purity_qubit_num = purity_qubit_num + 1 if ind_d == 0: break # padding the circuit name with Z's so that # all circuits will have names of the same length for _ in range(max_dim - purity_qubit_num): circ_purity[d].name += 'Z' # add measurement for purity rb for qind, qb in enumerate(qlist_flat): circ_purity[d].measure(qr[qb], cr[qind]) circ_purity[d].name += '_length_%d_seed_%d' \ % (length_index, seed + seed_offset) # add measurement for Non-Clifford cnot-dihedral rb # measure both the ground state |0...0> (circ) # and the |+...+> state (cnot-dihedral_circ) cnotdihedral_circ = qiskit.QuantumCircuit(qr, cr) cnotdihedral_interleaved_circ = qiskit.QuantumCircuit(qr, cr) if group_gates_type == 1: for _, qb in enumerate(qlist_flat): cnotdihedral_circ.h(qr[qb]) cnotdihedral_circ.barrier(qr[qb]) cnotdihedral_interleaved_circ.h(qr[qb]) cnotdihedral_interleaved_circ.barrier(qr[qb]) cnotdihedral_circ += circ cnotdihedral_interleaved_circ += circ_interleaved for _, qb in enumerate(qlist_flat): cnotdihedral_circ.barrier(qr[qb]) cnotdihedral_circ.h(qr[qb]) cnotdihedral_interleaved_circ.barrier(qr[qb]) cnotdihedral_interleaved_circ.h(qr[qb]) for qind, qb in enumerate(qlist_flat): cnotdihedral_circ.measure(qr[qb], cr[qind]) cnotdihedral_interleaved_circ.measure(qr[qb], cr[qind]) # add measurement for standard rb # qubits measure to the c registers as # they appear in the pattern for qind, qb in enumerate(qlist_flat): circ.measure(qr[qb], cr[qind]) # add measurement for interleaved rb circ_interleaved.measure(qr[qb], cr[qind]) circ.name = \ rb_circ_type + '_length_%d_seed_%d' % \ (length_index, seed + seed_offset) circ_interleaved.name = \ rb_circ_type + '_interleaved_length_%d_seed_%d' % \ (length_index, seed + seed_offset) if group_gates_type == 1: circ.name = rb_circ_type + '_Z_length_%d_seed_%d' % \ (length_index, seed + seed_offset) circ_interleaved.name = \ rb_circ_type + '_interleaved_Z_length_%d_seed_%d' % \ (length_index, seed + seed_offset) cnotdihedral_circ.name = \ rb_circ_type + '_X_length_%d_seed_%d' % \ (length_index, seed + seed_offset) cnotdihedral_interleaved_circ.name = \ rb_circ_type + '_interleaved_X_length_%d_seed_%d' % \ (length_index, seed + seed_offset) circuits[seed].append(circ) circuits_interleaved[seed].append(circ_interleaved) circuits_cnotdihedral[seed].append(cnotdihedral_circ) circuits_cnotdihedral_interleaved[seed].append( cnotdihedral_interleaved_circ) if is_purity: for d in range(npurity): circuits_purity[seed][d].append(circ_purity[d]) length_index += 1 # output of purity rb if is_purity: return circuits_purity, xdata, npurity # output of non-clifford cnot-dihedral interleaved rb if interleaved_gates is not None and group_gates_type == 1: return circuits, xdata, circuits_cnotdihedral, circuits_interleaved, \ circuits_cnotdihedral_interleaved # output of interleaved rb if interleaved_gates is not None: return circuits, xdata, circuits_interleaved # output of Non-Clifford cnot-dihedral rb if group_gates_type == 1: return circuits, xdata, circuits_cnotdihedral # output of standard (simultaneous) rb return circuits, xdata
def replace_q_indices(circuit, q_nums, qr): """ Take a circuit that is ordered from 0,1,2 qubits and replace 0 with the qubit label in the first index of q_nums, 1 with the second index... Args: circuit (QuantumCircuit): circuit to operate on q_nums (list): list of qubit indices qr (QuantumRegister): A quantum register to use for the output circuit Returns: QuantumCircuit: updated circuit """ new_circuit = qiskit.QuantumCircuit(qr) for instr, qargs, cargs in circuit.data: new_qargs = [ qr[q_nums[x]] for x in [arg.index for arg in qargs]] new_op = copy.deepcopy((instr, new_qargs, cargs)) new_circuit.data.append(new_op) return new_circuit def get_quantum_circuit(gatelist, num_qubits): """ Returns the circuit in the form of a QuantumCircuit object. Args: num_qubits (int): the number of qubits (dimension). gatelist (list): a list of gates. Returns: QuantumCircuit: A QuantumCircuit object. """ qr = qiskit.QuantumRegister(num_qubits) qc = qiskit.QuantumCircuit(qr) for op in gatelist: split = op.split() op_names = [split[0]] # temporary correcting the ops name since QuantumCircuit has no # attributes 'v' or 'w' yet: if op_names == ['v']: op_names = ['sdg', 'h'] elif op_names == ['w']: op_names = ['h', 's'] if op_names == ['u1']: qubits = [qr[int(x)] for x in split[2:]] theta = float(split[1]) else: qubits = [qr[int(x)] for x in split[1:]] for sub_op in op_names: operation = getattr(qiskit.QuantumCircuit, sub_op) if sub_op == 'u1': operation(qc, theta, *qubits) else: operation(qc, *qubits) return qc