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

# -*- 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=assignment-from-no-return,invalid-name

"""
Clifford Operator class
"""

import numpy as np
from qiskit.quantum_info import Pauli


[docs]class Clifford: """Clifford Operator Class.""" def __init__(self, num_qubits=None, table=None, phases=None): # Initialize an n-qubit Clifford table. # Use index for initializing 1 and 2 qubit Cliffords # If index is none initialize to identity. # It will be easier for internal functions to use a single np array # for the Clifford table rather than the Pauli class, and we can # convert rows to the Pauli class as needed. # Initialize internal variables self._num_qubits = None self._table = None self._phases = None # Initialize from table and phases if table is not None: # Store symplectic table self._table = np.array(table, dtype=bool) shape = self._table.shape if len(shape) != 2 or shape[0] != shape[1] or (shape[0] % 2): raise ValueError("Invalid symplectic table input") self._num_qubits = shape[0] // 2 # Store phases if phases: self._phases = np.array(phases, dtype=np.bool) shape = self._phases.shape if len(shape) != 1 or shape[0] != 2 * self._num_qubits: raise ValueError("Invalid phases") else: # Initialize all phases as zero self._phases = np.zeros(2 * self._num_qubits, dtype=np.bool) # Initialize from num_qubits only: else: if num_qubits is not None: self._num_qubits = num_qubits # Initialize symplectic table zeros = np.zeros((num_qubits, num_qubits), dtype=np.bool) iden = np.eye(num_qubits, dtype=np.bool) self._table = np.block([[zeros, iden], [iden, zeros]]) # Initialize phases self._phases = np.zeros(2 * num_qubits, dtype=np.bool) def __repr__(self): # TODO: truncate output for large tables output = 'Clifford(phases={},\n'.format(repr(self._phases.tolist())) table_str = ' table=[' pad = "".join(len(table_str) * [' ']) for j, row in enumerate(self._table): if j > 0: table_str += pad table_str += repr(row.tolist()) if j < 2 * self.num_qubits - 1: table_str += ",\n" else: table_str += "])" return output + table_str # --------------------------------------------------------------------- # Data accessors # --------------------------------------------------------------------- @property def num_qubits(self): """Return the number of qubits for the Clifford.""" return self._num_qubits @property def table(self): """Return the Clifford table.""" return self._table @property def phases(self): """Return the Clifford phases.""" return self._phases
[docs] def __getitem__(self, index): """Get element from internal symplectic table.""" return self._table[index]
def __setitem__(self, index, value): """Set element of internal symplectic table.""" if isinstance(value, Pauli): # Update from Pauli object self._table[index] = np.block([value.z, value.x]) else: # Update table as Numpy array self._table[index] = value # --------------------------------------------------------------------- # Interface with Pauli object # ---------------------------------------------------------------------
[docs] def stabilizer(self, qubit): """Return the qubit stabilizer as a Pauli object.""" num_qubits = self._num_qubits z = self._table[num_qubits + qubit, 0:num_qubits] x = self._table[num_qubits + qubit, num_qubits:2 * num_qubits] return Pauli(z=z, x=x)
[docs] def update_stabilizer(self, qubit, pauli): """Update the qubit stabilizer row from a Pauli object.""" self[self._num_qubits + qubit] = pauli
[docs] def destabilizer(self, row): """Return the destabilizer as a Pauli object.""" num_qubits = self._num_qubits z = self._table[row, 0:num_qubits] x = self._table[row, num_qubits:2 * num_qubits] return Pauli(z=z, x=x)
[docs] def update_destabilizer(self, qubit, pauli): """Update the qubit destabilizer row from a Pauli object.""" self[qubit] = pauli
# --------------------------------------------------------------------- # JSON / Dict conversion # ---------------------------------------------------------------------
[docs] def as_dict(self): """Return dictionary (JSON) represenation of Clifford object""" # Modify later if we want to include i and -i. phase_coeffs = ['', '-'] stabilizers = [] for qubit in range(self.num_qubits): label = self.stabilizer(qubit).to_label() phase = self.phases[self.num_qubits + qubit] stabilizers.append(phase_coeffs[phase] + label) destabilizers = [] for qubit in range(self.num_qubits): label = self.destabilizer(qubit).to_label() phase = self.phases[qubit] destabilizers.append(phase_coeffs[phase] + label) return {"stabilizers": stabilizers, "destabilizers": destabilizers}
[docs] @classmethod def from_dict(cls, clifford_dict): """Load a Clifford from a dictionary.""" # Validation if not isinstance(clifford_dict, dict) or \ 'stabilizers' not in clifford_dict or \ 'destabilizers' not in clifford_dict: raise ValueError("Invalid input Clifford dictionary.") stabilizers = clifford_dict['stabilizers'] destabilizers = clifford_dict['destabilizers'] if len(stabilizers) != len(destabilizers): raise ValueError( "Invalid Clifford dict: length of stabilizers and " "destabilizers do not match.") num_qubits = len(stabilizers) # Helper function def get_row(label): """Return the Pauli object and phase for stabilizer.""" if label[0] in ['I', 'X', 'Y', 'Z']: pauli = Pauli.from_label(label) phase = 0 elif label[0] == '+': pauli = Pauli.from_label(label[1:]) phase = 0 elif label[0] == '-': pauli = Pauli.from_label(label[1:]) phase = 1 return pauli, phase # Generate identity Clifford on number of qubits clifford = cls(num_qubits) # Update stabilizers for qubit, label in enumerate(stabilizers): pauli, phase = get_row(label) clifford[num_qubits + qubit] = pauli clifford.phases[num_qubits + qubit] = phase # Update destabilizers for qubit, label in enumerate(destabilizers): pauli, phase = get_row(label) clifford[qubit] = pauli clifford.phases[qubit] = phase return clifford
# --------------------------------------------------------------------- # Unique Clifford index # ---------------------------------------------------------------------
[docs] def index(self): """ Returns a unique index for the Clifford. Returns: int: A unique index (integer) for the Clifford object. """ mat = self.table mat = mat.reshape(mat.size) ret = int(0) for bit in mat: ret = (ret << 1) | int(bit) mat = self.phases mat = mat.reshape(mat.size) for bit in mat: ret = (ret << 1) | int(bit) return ret
# --------------------------------------------------------------------- # Canonical gate operations # --------------------------------------------------------------------- # NOTE: These might change based on changes to QuantumCircuit API. # They should mimic the circuit API as much as possible.
[docs] def x(self, qubit): """Apply a Pauli "x" gate to a qubit.""" self._phases = np.logical_xor(self._phases, self._table[:, qubit])
[docs] def y(self, qubit): """Apply an Pauli "y" gate to a qubit.""" iz, ix = qubit, self._num_qubits + qubit zx_xor = np.logical_xor(self._table[:, iz], self._table[:, ix]) self._phases = np.logical_xor(self._phases, zx_xor)
[docs] def z(self, qubit): """Apply an Pauli "z" gate to qubit.""" ix = self._num_qubits + qubit self._phases = np.logical_xor(self._phases, self._table[:, ix])
[docs] def h(self, qubit): """Apply an Hadamard "h" gate to qubit.""" iz, ix = qubit, self._num_qubits + qubit zx_and = np.logical_and(self._table[:, ix], self._table[:, iz]) self._phases = np.logical_xor(self._phases, zx_and) # Cache X column for qubit x_cache = self._table[:, ix].copy() # Swap X and Z columns for qubit self._table[:, ix] = self._table[:, iz] # Does this need to be a copy? self._table[:, iz] = x_cache
[docs] def s(self, qubit): """Apply a phase "s" gate to qubit.""" iz, ix = qubit, self._num_qubits + qubit zx_and = np.logical_and(self._table[:, ix], self._table[:, iz]) self._phases = np.logical_xor(self._phases, zx_and) self._table[:, iz] = np.logical_xor(self._table[:, ix], self._table[:, iz])
[docs] def sdg(self, qubit): """Apply an adjoint phase "sdg" gate to qubit.""" # TODO: change direct table update if more efficient self.z(qubit) self.s(qubit)
[docs] def v(self, qubit): """Apply v gate v = sdg.h .""" # TODO: change direct table update if more efficient self.sdg(qubit) self.h(qubit)
[docs] def w(self, qubit): """Apply w gate w = v.v .""" # TODO: change direct table update if more efficient self.h(qubit) self.s(qubit)
[docs] def cx(self, qubit_ctrl, qubit_trgt): """Apply a Controlled-NOT "cx" gate.""" # Helper indices for stabilizer columns iz_c, ix_c = qubit_ctrl, self.num_qubits + qubit_ctrl iz_t, ix_t = qubit_trgt, self.num_qubits + qubit_trgt # Compute phase tmp = np.logical_xor(self._table[:, ix_t], self._table[:, iz_c]) tmp = np.logical_xor(1, tmp) # Shelly: fixed misprint in logical tmp = np.logical_and(self._table[:, iz_t], tmp) tmp = np.logical_and(self._table[:, ix_c], tmp) self._phases ^= tmp # Update stabilizers self._table[:, ix_t] = np.logical_xor(self._table[:, ix_t], self._table[:, ix_c]) self._table[:, iz_c] = np.logical_xor(self._table[:, iz_t], self._table[:, iz_c])
[docs] def cz(self, qubit_ctrl, qubit_trgt): """Apply a Controlled-z "cz" gate.""" # TODO: change direct table update if more efficient self.h(qubit_trgt) self.cx(qubit_ctrl, qubit_trgt) self.h(qubit_trgt)
[docs] def swap(self, qubit0, qubit1): """Apply SWAP gate between two qubits.""" # TODO: change direct swap of required rows and cols in table self.cx(qubit0, qubit1) self.cx(qubit1, qubit0) self.cx(qubit0, qubit1)