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

# -*- 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.

# pylint: disable=invalid-name

# NOTE: this is because BasicUtils doesn't define any arguments on it's methods
# so the implementations here don't match the abstract class. This needs to
# be fixed or reworked without an abstract class
# pylint: disable=arguments-differ

"""Advanced Clifford operations needed for randomized benchmarking."""

import numpy as np
from .Clifford import Clifford
from .basic_utils import BasicUtils

try:
    import cPickle as pickle
except ImportError:
    import pickle


[docs]class CliffordUtils(BasicUtils): """Class for util functions for the Clifford group.""" def __init__(self, num_qubits=2, group_tables=None, elmnt=None, gatelist=None, elmnt_key=None): """ Args: num_qubits (int): number of qubits, dimension of the Clifford object. group_tables (dict): table of all the Clifford objects of a given dimension. elmnt (Clifford): a Clifford group element. elmnt_key (str): a unique index of a Clifford object. gatelist (list): a list of gates corresponding to a Clifford object. """ self._num_qubits = num_qubits self._group_tables = group_tables self._elmnt = elmnt self._elmnt_key = elmnt_key self._gatelist = gatelist
[docs] def num_qubits(self): """Return the number of qubits of the Clifford object.""" return self._num_qubits
[docs] def group_tables(self): """Return the Clifford group tables.""" return self._group_tables
[docs] def elmnt(self): """Return a Clifford object.""" return self._elmnt
[docs] def elmnt_key(self): """Return a unique index of a Clifford object.""" return self._elmnt_key
[docs] def gatelist(self): """Return a list of gates corresponding to a Clifford object.""" return self._gatelist
# ---------------------------------------------------------------------------------------- # Functions that convert to/from a Clifford object # ----------------------------------------------------------------------------------------
[docs] def compose_gates(self, cliff, gatelist): """ Add gates to a Clifford object from a list of gates. Args: cliff (Clifford): a Clifford class object. gatelist (list): a list of gates. Returns: Clifford: A Clifford class object, after composing cliff and the gates from gatelist. Raises: ValueError: unknown gate type. """ for op in gatelist: split = op.split() q1 = int(split[1]) if split[0] == 'v': cliff.v(q1) elif split[0] == 'w': cliff.w(q1) elif split[0] == 'x': cliff.x(q1) elif split[0] == 'y': cliff.y(q1) elif split[0] == 'z': cliff.z(q1) elif split[0] == 'cx': cliff.cx(q1, int(split[2])) elif split[0] == 'h': cliff.h(q1) elif split[0] == 's': cliff.s(q1) elif split[0] == 'sdg': cliff.sdg(q1) else: raise ValueError("Unknown gate type: ", op) self._gatelist = gatelist self._elmnt = cliff return cliff
[docs] def clifford_from_gates(self, num_qubits, gatelist): """ Generate a Clifford object from a list of gates. Args: num_qubits (int): the number of qubits for the Clifford. gatelist (list): a list of gates. Returns: Clifford: A Clifford class object corresponding to composing the given list of gates. """ cliff = Clifford(num_qubits) new_cliff = self.compose_gates(cliff, gatelist) return new_cliff
# -------------------------------------------------------- # Add gates to Cliffords # --------------------------------------------------------
[docs] def pauli_gates(self, gatelist, q, pauli): """Append a pauli gate on qubit q to a given list of gates. Args: gatelist (list): a list of gates. q (int): an index of the qubit. pauli (int): an integer indicating the pauli gate: * 1 - for pauli-Z gate. * 2 - for pauli-X gate. * 3 - for pauli-Y gate. Returns: list: A list of gates, after appending a given pauli gate on qubit q. """ if pauli == 2: gatelist.append('x ' + str(q)) elif pauli == 3: gatelist.append('y ' + str(q)) elif pauli == 1: gatelist.append('z ' + str(q))
[docs] def h_gates(self, gatelist, q, h): """Append a hadamard gate on qubit q to a given list of gates. Args: gatelist (list): a list of gates. q (int): an index of the qubit. h (int): an integer indicating whether or not to apply a hadamard gate: * 1 - for H gate. * 0 - no H gate. Returns: list: A list of gates, after appending a hadamard gate on qubit q. """ if h == 1: gatelist.append('h ' + str(q))
[docs] def v_gates(self, gatelist, q, v): """Adds an axis-swap gate V or W on qubit q to a given list of gates. The V gate is defined as: V=HSHS = [[0,1],[1,1]]. It makes a rotation of the paulis: Z->X->Y->Z V is of order 3, and two V-gates is a W-gate, so: W=VV and WV=I. Args: gatelist (list): a list of gates. q (int): an index of the qubit. v (int): an integer indicating the gate: * 1 - for V gate. * 2 - for W gate. Returns: list: A list of gates, after appending a gate V or W on qubit q. """ if v == 1: gatelist.append('v ' + str(q)) elif v == 2: gatelist.append('w ' + str(q))
[docs] def cx_gates(self, gatelist, ctrl, tgt): """Adds a controlled-x gate on qubits ctrl and tgt to a given list of gates. Args: gatelist (list): a list of gates. ctrl (int): an index of the control qubit. tgt (int): an index of the target qubit. Returns: list: A list of gates, after appending a controlled-x gate on two qubits. """ gatelist.append('cx ' + str(ctrl) + ' ' + str(tgt))
# -------------------------------------------------------- # Create a 1 or 2 Qubit Clifford based on a unique index # --------------------------------------------------------
[docs] def clifford1_gates(self, idx: int): """ Make a single qubit Clifford gate. Args: idx: the index (modulo 24) of a single qubit Clifford. Returns: list: A single qubit Clifford gate. """ gatelist = [] # Cannonical Ordering of Cliffords 0,...,23 cannonicalorder = idx % 24 pauli = np.mod(cannonicalorder, 4) rotation = np.mod(cannonicalorder // 4, 3) h_or_not = np.mod(cannonicalorder // 12, 2) self.h_gates(gatelist, 0, h_or_not) self.v_gates(gatelist, 0, rotation) self.pauli_gates(gatelist, 0, pauli) return gatelist
[docs] def clifford2_gates(self, idx: int): """ Make a 2-qubit Clifford gate. Args: idx: the index (modulo 11520) of a two-qubit Clifford. Returns: list: A 2-qubit Clifford gate. """ gatelist = [] cannon = idx % 11520 pauli = np.mod(cannon, 16) symp = cannon // 16 if symp < 36: # 1-qubit Cliffords Class r0 = np.mod(symp, 3) r1 = np.mod(symp // 3, 3) h0 = np.mod(symp // 9, 2) h1 = np.mod(symp // 18, 2) self.h_gates(gatelist, 0, h0) self.h_gates(gatelist, 1, h1) self.v_gates(gatelist, 0, r0) self.v_gates(gatelist, 1, r1) elif symp < 360: # CNOT-like Class symp = symp - 36 r0 = np.mod(symp, 3) r1 = np.mod(symp // 3, 3) r2 = np.mod(symp // 9, 3) r3 = np.mod(symp // 27, 3) h0 = np.mod(symp // 81, 2) h1 = np.mod(symp // 162, 2) self.h_gates(gatelist, 0, h0) self.h_gates(gatelist, 1, h1) self.v_gates(gatelist, 0, r0) self.v_gates(gatelist, 1, r1) self.cx_gates(gatelist, 0, 1) self.v_gates(gatelist, 0, r2) self.v_gates(gatelist, 1, r3) elif symp < 684: # iSWAP-like Class symp = symp - 360 r0 = np.mod(symp, 3) r1 = np.mod(symp // 3, 3) r2 = np.mod(symp // 9, 3) r3 = np.mod(symp // 27, 3) h0 = np.mod(symp // 81, 2) h1 = np.mod(symp // 162, 2) self.h_gates(gatelist, 0, h0) self.h_gates(gatelist, 1, h1) self.v_gates(gatelist, 0, r0) self.v_gates(gatelist, 1, r1) self.cx_gates(gatelist, 0, 1) self.cx_gates(gatelist, 1, 0) self.v_gates(gatelist, 0, r2) self.v_gates(gatelist, 1, r3) else: # SWAP Class symp = symp - 684 r0 = np.mod(symp, 3) r1 = np.mod(symp // 3, 3) h0 = np.mod(symp // 9, 2) h1 = np.mod(symp // 18, 2) self.h_gates(gatelist, 0, h0) self.h_gates(gatelist, 1, h1) self.v_gates(gatelist, 0, r0) self.v_gates(gatelist, 1, r1) self.cx_gates(gatelist, 0, 1) self.cx_gates(gatelist, 1, 0) self.cx_gates(gatelist, 0, 1) self.pauli_gates(gatelist, 0, np.mod(pauli, 4)) self.pauli_gates(gatelist, 1, pauli // 4) return gatelist
# -------------------------------------------------------- # Create a 1 or 2 Qubit Clifford tables # --------------------------------------------------------
[docs] def clifford2_gates_table(self): """ Generate a table of all 2-qubit Clifford gates. Returns: dict: A table of all 2-qubit Clifford gates. """ cliffords2 = {} for i in range(11520): circ = self.clifford2_gates(i) key = self.clifford_from_gates(2, circ).index() cliffords2[key] = circ return cliffords2
[docs] def clifford1_gates_table(self): """ Generate a table of all 1-qubit Clifford gates. Returns: dict: A table of all 1-qubit Clifford gates. """ cliffords1 = {} for i in range(24): circ = self.clifford1_gates(i) key = self.clifford_from_gates(1, circ).index() cliffords1[key] = circ return cliffords1
[docs] def pickle_clifford_table(self, picklefile='cliffords2.pickle', num_qubits=2): """ Create pickled versions of the 1 or 2 qubit Clifford group tables. Args: picklefile (str): pickle file name. num_qubits (int): number of qubits of the Clifford object. Raises: ValueError: number of qubits bigger than 2 is not supported. """ cliffords = {} if num_qubits == 1: cliffords = self.clifford1_gates_table() elif num_qubits == 2: cliffords = self.clifford2_gates_table() else: raise ValueError( "number of qubits bigger than is not supported for pickle") with open(picklefile, "wb") as pf: pickle.dump(cliffords, pf) pf.close()
[docs] def load_clifford_table(self, picklefile='cliffords2.pickle'): """ Load pickled files of the tables of 1 and 2 qubit Clifford group tables. Args: picklefile (str): pickle file name. Returns: dict: A table of all the 1 and 2 qubit Clifford objects. """ with open(picklefile, "rb") as pf: pickletable = pickle.load(pf) pf.close() return pickletable
[docs] def load_tables(self, num_qubits): """ Return the Clifford group tables. Args: num_qubits (int): number of qubits for the Clifford object. Returns: dict: A table of all the Clifford objects. Raises: ValueError: number of qubits bigger than 2 is not supported. """ # load the clifford tables, but only if we're using that particular # num_qubits if num_qubits == 1: # 1Q Cliffords, load table programmatically clifford_tables = self.clifford1_gates_table() elif num_qubits == 2: # 2Q Cliffords # Try to load the table in from file. If it doesn't exist then make # the file try: clifford_tables = self.load_clifford_table( picklefile='cliffords%d.pickle' % num_qubits) except (OSError, EOFError): # table doesn't exist, so save it # this will save time next run print('Making the n=%d Clifford Table' % num_qubits) self.pickle_clifford_table( picklefile='cliffords%d.pickle' % num_qubits, num_qubits=num_qubits) clifford_tables = self.load_clifford_table( picklefile='cliffords%d.pickle' % num_qubits) except pickle.UnpicklingError: # handle error clifford_tables = self.clifford2_gates_table() else: raise ValueError("The number of qubits should be only 1 or 2") self._group_tables = clifford_tables return clifford_tables
# -------------------------------------------------------- # Main function that generates a random clifford gate # --------------------------------------------------------
[docs] def random_gates(self, num_qubits, rand_seed=None): """ Pick a random Clifford gate on num_qubits. Args: num_qubits (int): dimension of the Clifford. rand_seed (int): seed for the random number generator Returns: list: A 1 or 2 qubit random Clifford gate. Raises: ValueError: number of qubits bigger than 2 is not supported. TypeError: If rand_seed is not an integer """ if rand_seed is not None: if not isinstance(rand_seed, int): raise TypeError("Random seed number should be an integer") np.random.seed(rand_seed) if num_qubits == 1: cliff_gatelist = self.clifford1_gates(np.random.randint(0, 24)) elif num_qubits == 2: cliff_gatelist = self.clifford2_gates(np.random.randint(0, 11520)) else: raise ValueError("The number of qubits should be only 1 or 2") self._gatelist = cliff_gatelist return cliff_gatelist
# -------------------------------------------------------- # Main function that calculates an inverse of a clifford gate # --------------------------------------------------------
[docs] def find_inverse_gates(self, num_qubits, gatelist): """ Find the inverse of a Clifford gate. Args: num_qubits (int): dimension of the Clifford object. gatelist (list): a Clifford gate. Returns: list: An inverse Clifford gate. Raises: ValueError: number of qubits bigger than 2 is not supported. """ if num_qubits in (1, 2): inv_gatelist = gatelist.copy() inv_gatelist.reverse() # replace v by w and w by v for i, _ in enumerate(inv_gatelist): split = inv_gatelist[i].split() if split[0] == 'v': inv_gatelist[i] = 'w ' + split[1] elif split[0] == 'w': inv_gatelist[i] = 'v ' + split[1] return inv_gatelist raise ValueError("The number of qubits should be only 1 or 2")
[docs] def find_key(self, cliff, num_qubits): """ Find the Clifford index. Args: cliff (Clifford): a Clifford object. num_qubits (int): dimension of the Clifford object. Returns: int: An integer which is the Clifford index in the group table. """ G_table = self.load_tables(num_qubits) assert cliff.index() in G_table, \ "inverse not found in lookup table!\n%s" % cliff return cliff.index()