From 1f324eeeb17d550359a0190d076b509e8e1648eb Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 18 Feb 2020 13:43:59 -0500 Subject: [PATCH 01/17] Add Clifford operator class --- qiskit/quantum_info/__init__.py | 2 +- qiskit/quantum_info/operators/__init__.py | 2 +- .../operators/symplectic/__init__.py | 1 + .../operators/symplectic/clifford.py | 313 ++++++++++++++++++ .../symplectic/clifford_append_gate.py | 298 +++++++++++++++++ 5 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 qiskit/quantum_info/operators/symplectic/clifford.py create mode 100644 qiskit/quantum_info/operators/symplectic/clifford_append_gate.py diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 3603b5cf114..1e25cb17a4f 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -118,7 +118,7 @@ from .operators.measures import process_fidelity from .operators import average_gate_fidelity from .operators import gate_error -from .operators.symplectic import PauliTable, StabilizerTable +from .operators.symplectic import Clifford, PauliTable, StabilizerTable from .operators.symplectic import pauli_basis from .states import Statevector, DensityMatrix diff --git a/qiskit/quantum_info/operators/__init__.py b/qiskit/quantum_info/operators/__init__.py index a8a748d6ceb..14c59df1d39 100644 --- a/qiskit/quantum_info/operators/__init__.py +++ b/qiskit/quantum_info/operators/__init__.py @@ -21,5 +21,5 @@ from .quaternion import Quaternion from .measures import process_fidelity, average_gate_fidelity, gate_error -from .symplectic import PauliTable, StabilizerTable +from .symplectic import Clifford, PauliTable, StabilizerTable from .symplectic import pauli_basis diff --git a/qiskit/quantum_info/operators/symplectic/__init__.py b/qiskit/quantum_info/operators/symplectic/__init__.py index 58cb82e6099..9608630c7bc 100644 --- a/qiskit/quantum_info/operators/symplectic/__init__.py +++ b/qiskit/quantum_info/operators/symplectic/__init__.py @@ -19,3 +19,4 @@ from .pauli_table import PauliTable from .pauli_utils import pauli_basis from .stabilizer_table import StabilizerTable +from .clifford import Clifford diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py new file mode 100644 index 00000000000..7844211429d --- /dev/null +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017, 2020 BM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +""" +Clifford operator class. +""" + +import numpy as np + +from qiskit import QiskitError +from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit.quantum_info.operators.scalar_op import ScalarOp +from qiskit.quantum_info.operators.symplectic.stabilizer_table import StabilizerTable +from qiskit.quantum_info.operators.symplectic.clifford_append_gate import append_gate + + +class Clifford(BaseOperator): + """Clifford table operator class""" + + def __init__(self, data): + """Initialize an operator object.""" + + # Initialize from another Clifford by sharing the underlying + # StabilizerTable + if isinstance(data, Clifford): + self._table = data._table + + # Initialize from ScalarOp as N-qubit identity discarding any global phase + elif isinstance(data, ScalarOp): + if not data.is_unitary() or set(data._input_dims) != set([2]): + raise QiskitError("Can only initalize from N-qubit identity ScalarOp.") + self._table = StabilizerTable( + np.eye(2 * len(data._input_dims), dtype=np.bool)) + + # Initialize from a QuantumCircuit or Instruction object + elif isinstance(data, (QuantumCircuit, Instruction)): + self._table = Clifford.from_instruction(data)._table + + # Initialize StabilizerTable directly from the data + else: + self._table = StabilizerTable(data) + + # Validate shape of StabilizerTable + if self._table.size != 2 * self._table.n_qubits: + raise QiskitError( + 'Invalid Clifford (number of rows {0} != {1}). An {2}-qubit' + ' Clifford table requires {1} rows.'.format( + self._table.size, 2 * self._table.n_qubits, self.n_qubits)) + + # TODO: Should we check the input array is a valid Clifford table? + # This should be done by the `is_unitary` method. + + # Initialize BaseOperator + dims = self._table.n_qubits * (2,) + super().__init__(dims, dims) + + def __repr__(self): + return 'Clifford({})'.format(repr(self.table)) + + def __str__(self): + return 'Clifford: Stabilizer = {}, Destabilizer = {}'.format( + str(self.stabilizer.to_labels()), + str(self.destabilizer.to_labels())) + + def __eq__(self, other): + """Check if two Clifford tables are equal""" + return super().__eq__(other) and self._table == other._table + + # --------------------------------------------------------------------- + # Attributes + # --------------------------------------------------------------------- + def __getitem__(self, key): + """Return a stabilizer Pauli row""" + return self._table.__getitem__(key) + + def __setitem__(self, key, value): + """Set a stabilizer Pauli row""" + self._table.__setitem__(key, value) + + @property + def n_qubits(self): + """The number of qubits for the Clifford.""" + return self._table._n_qubits + + @property + def table(self): + """Return StabilizerTable""" + return self._table + + @table.setter + def table(self, value): + """Set the stabilizer table""" + # Note that is setup so it can't change the size of the Clifford + # It can only replace the contents of the StabilizerTable with + # another StabilizerTable of the same size. + if not isinstance(value, StabilizerTable): + value = StabilizerTable(value) + self._table._array[:, :] = value._table._array + self._table._phase[:] = value._table._phase + + @property + def stabilizer(self): + """Return the stabilizer block of the StabilizerTable.""" + return StabilizerTable(self._table[self.n_qubits:2*self.n_qubits]) + + @stabilizer.setter + def stabilizer(self, value): + """Set the value of stabilizer block of the StabilizerTable""" + inds = slice(self.n_qubits, 2*self.n_qubits) + self._table.__setitem__(inds, value) + + @property + def destabilizer(self): + """Return the destabilizer block of the StabilizerTable.""" + return StabilizerTable(self._table[0:self.n_qubits]) + + @destabilizer.setter + def destabilizer(self, value): + """Set the value of destabilizer block of the StabilizerTable""" + inds = slice(0, self.n_qubits) + self._table.__setitem__(inds, value) + + # --------------------------------------------------------------------- + # Utility Operator methods + # --------------------------------------------------------------------- + + def is_unitary(self, atol=None, rtol=None): + """Return True if the Clifford table is valid.""" + # A valid Clifford is always unitary, so this function is really + # checking that the underlying Stabilizer table array is a valid + # Clifford array. + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def to_matrix(self): + """Convert operator to Numpy matrix.""" + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def to_operator(self): + """Convert to an Operator object.""" + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + # --------------------------------------------------------------------- + # BaseOperator Abstract Methods + # --------------------------------------------------------------------- + + def conjugate(self): + """Return the conjugate of the Clifford.""" + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def transpose(self): + """Return the transpose of the Clifford.""" + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def compose(self, other, qargs=None, front=False): + """Return the composed operator. + + Args: + other (Clifford): an operator object. + qargs (list or None): a list of subsystem positions to apply + other on. If None apply on all + subsystems [default: None]. + front (bool): If True compose using right operator multiplication, + instead of left multiplication [default: False]. + + Returns: + Clifford: The operator self @ other. + + Raise: + QiskitError: if operators have incompatible dimensions for + composition. + + Additional Information: + Composition (``@``) is defined as `left` matrix multiplication for + matrix operators. That is that ``A @ B`` is equal to ``B * A``. + Setting ``front=True`` returns `right` matrix multiplication + ``A * B`` and is equivalent to the :meth:`dot` method. + """ + if qargs is None: + qargs = getattr(other, 'qargs', None) + + if not isinstance(other, Clifford): + other = Clifford(other) + + # Validate dimensions. Note we don't need to get updated input or + # output dimensions from `_get_compose_dims` as the dimensions of the + # Clifford object can't be changed by composition + self._get_compose_dims(other, qargs, front) + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def dot(self, other, qargs=None): + """Return the right multiplied operator self * other. + + Args: + other (Clifford): an operator object. + qargs (list or None): a list of subsystem positions to apply + other on. If None apply on all + subsystems [default: None]. + + Returns: + Clifford: The operator self * other. + + Raises: + QiskitError: if operators have incompatible dimensions for + composition. + """ + return super().dot(other, qargs=qargs) + + def tensor(self, other): + """Return the tensor product operator self ⊗ other. + + Args: + other (Clifford): a operator subclass object. + + Returns: + Clifford: the tensor product operator self ⊗ other. + """ + if not isinstance(other, Clifford): + other = Clifford(other) + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + def expand(self, other): + """Return the tensor product operator other ⊗ self. + + Args: + other (Clifford): an operator object. + + Returns: + Clifford: the tensor product operator other ⊗ self. + """ + if not isinstance(other, Clifford): + other = Clifford(other) + + # TODO: IMPLEMENT ME! + + raise NotImplementedError( + 'This method has not been implemented for Clifford operators yet.') + + # --------------------------------------------------------------------- + # Representation conversions + # --------------------------------------------------------------------- + + def to_dict(self): + """Return dictionary represenation of Clifford object""" + return { + "stabilizer": self.stabilizer.to_labels(), + "destabilizer": self.destabilizer.to_labels() + } + + @staticmethod + def from_dict(obj): + """Load a Clifford from a dictionary""" + destabilizer = StabilizerTable.from_labels(obj.get('destabilizer')) + stabilizer = StabilizerTable.from_labels(obj.get('stabilizer')) + return Clifford(destabilizer + stabilizer) + + @staticmethod + def from_instruction(instruction): + """Initialize from a QuantumCircuit or Instruction. + + Args: + instruction (QuantumCircuit or Instruction): instruction to + initialize. + + Returns: + Clifford: the Clifford object for the instruction. + + Raises: + QiskitError: if the input instruction is non-Clifford or contains + classical register instruction. + """ + if not isinstance(instruction, (QuantumCircuit, Instruction)): + raise QiskitError("Input must be a QuantumCircuit or Instruction") + + # Convert circuit to an instruction + if isinstance(instruction, QuantumCircuit): + instruction = instruction.to_instruction() + + # Initialize an identity Clifford + clifford = Clifford(np.eye(2 * instruction.num_qubits)) + append_gate(clifford, instruction) + return clifford diff --git a/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py b/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py new file mode 100644 index 00000000000..f49fed97a26 --- /dev/null +++ b/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017, 2020 BM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +""" +Clifford class gate update utility function +""" +# pylint: disable=invalid-name + +from qiskit import QiskitError + + +# --------------------------------------------------------------------- +# Apply Clifford Gates +# --------------------------------------------------------------------- + +def append_gate(clifford, gate, qargs=None): + """Update Clifford inplace by applying a Clifford gate. + + Args: + clifford (Clifford): the Clifford to update. + gate (Gate or str): the gate or composite gate to apply. + qargs (list or None): The qubits to apply gate to. + + Returns: + Clifford: the updated Clifford. + """ + if qargs is None: + qargs = list(range(clifford.n_qubits)) + + # Basis Clifford Gates + basis_1q = { + 'i': append_i, 'id': append_i, 'iden': append_i, + 'x': append_x, 'y': append_y, 'z': append_z, 'h': append_h, + 's': append_s, 'sdg': append_sdg, 'sinv': append_sdg, + 'v': append_v, 'w': append_w + } + basis_2q = { + 'cx': append_cx, 'cz': append_cz, 'swap': append_swap + } + + # Non-clifford gates + non_clifford = ['t', 'tdg', 'ccx', 'ccz'] + + if isinstance(gate, str): + # Check if gate is a valid Clifford basis gate string + if gate not in basis_1q and gate not in basis_2q: + raise QiskitError("Invalid Clifford gate name string {}".format(gate)) + name = gate + else: + # Assume gate is an Instruction + name = gate.name + + # Apply gate if it is a Clifford basis gate + if name in non_clifford: + raise QiskitError( + "Cannot update Clifford with non-Clifford gate {}".format(name)) + if name in basis_1q: + if len(qargs) != 1: + raise QiskitError("Invalid qubits for 1-qubit gate.") + return basis_1q[name](clifford, qargs[0]) + if name in basis_2q: + if len(qargs) != 2: + raise QiskitError("Invalid qubits for 2-qubit gate.") + return basis_2q[name](clifford, qargs[0], qargs[1]) + + # If not a Clifford basis gate we try to unroll the gate, + # raising an exception if unrolling reaches a non-Clifford gate. + # TODO: We could check for also check u3 params to see if they + # are a single qubit Clifford gate rather than raise an exception. + if gate.definition is None: + raise QiskitError('Cannot apply Instruction: {}'.format(gate.name)) + for instr, qregs, cregs in gate.definition: + if cregs: + raise QiskitError( + 'Cannot apply Instruction with classical registers: {}'.format( + instr.name)) + # Get the integer position of the flat register + new_qubits = [qargs[tup.index] for tup in qregs] + append_gate(clifford, instr, new_qubits) + return clifford + + +# --------------------------------------------------------------------- +# Helper functions for applying basis gates +# --------------------------------------------------------------------- + +def append_i(clifford, qubit): + """Apply an I gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + return clifford + + +def append_x(clifford, qubit): + """Apply an X gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + clifford.table.phase ^= clifford.table.Z[:, qubit] + return clifford + + +def append_y(clifford, qubit): + """Apply a Y gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + clifford.table.phase ^= x ^ z + return clifford + + +def append_z(clifford, qubit): + """Apply an Z gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + clifford.table.phase ^= clifford.table.X[:, qubit] + return clifford + + +def append_h(clifford, qubit): + """Apply a H gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + clifford.table.phase ^= x & z + tmp = x.copy() + x[:] = z + z[:] = tmp + return clifford + + +def append_s(clifford, qubit): + """Apply an S gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + + clifford.table.phase ^= x & z + z ^= x + return clifford + + +def append_sdg(clifford, qubit): + """Apply an Sdg gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + clifford.table.phase ^= x & ~z + z ^= x + return clifford + + +def append_v(clifford, qubit): + """Apply a V gate to a Clifford. + + This is equivalent to an Sdg gate followed by a H gate. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + clifford.table.phase ^= False + tmp = x.copy() + x ^= z + z[:] = tmp + return clifford + + +def append_w(clifford, qubit): + """Apply a W gate to a Clifford. + + This is equivalent to two V gates. + + Args: + clifford (Clifford): a Clifford. + qubit (int): gate qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x = clifford.table.X[:, qubit] + z = clifford.table.Z[:, qubit] + clifford.table.phase ^= False + tmp = z.copy() + z ^= x + x[:] = tmp + return clifford + + +def append_cx(clifford, control, target): + """Apply a CX gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + control (int): gate control qubit index. + target (int): gate target qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x0 = clifford.table.X[:, control] + z0 = clifford.table.Z[:, control] + x1 = clifford.table.X[:, target] + z1 = clifford.table.Z[:, target] + clifford.table.phase ^= (x1 ^ z0 ^ True) & z1 & x0 + x1 ^= x0 + z0 ^= z1 + return clifford + + +def append_cz(clifford, control, target): + """Apply a CZ gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + control (int): gate control qubit index. + target (int): gate target qubit index. + + Returns: + Clifford: the updated Clifford. + """ + x0 = clifford.table.X[:, control] + z0 = clifford.table.Z[:, control] + x1 = clifford.table.X[:, target] + z1 = clifford.table.Z[:, target] + clifford.table.phase ^= (z1 ^ z0 ^ True) & x1 & x0 + z1 ^= x0 + z0 ^= x1 + return clifford + + +def append_swap(clifford, qubit0, qubit1): + """Apply a Swap gate to a Clifford. + + Args: + clifford (Clifford): a Clifford. + qubit0 (int): first qubit index. + qubit1 (int): second qubit index. + + Returns: + Clifford: the updated Clifford. + """ + clifford.table.X[:, [qubit0, qubit1]] = clifford.table.X[:, [qubit1, qubit0]] + clifford.table.Z[:, [qubit0, qubit1]] = clifford.table.Z[:, [qubit1, qubit0]] + return clifford From 8a6eb76eac7431a0665144c8d6f03e6d72953064 Mon Sep 17 00:00:00 2001 From: sethmerkel <49210444+sethmerkel@users.noreply.github.com> Date: Wed, 11 Mar 2020 11:19:32 -0400 Subject: [PATCH 02/17] Add compose, tensor, expand, and is_unitary Co-Authored-By: Christopher J. Wood --- .../operators/symplectic/clifford.py | 217 +++++++++++++++--- 1 file changed, 189 insertions(+), 28 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 7844211429d..defdc639f13 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -134,10 +134,16 @@ def is_unitary(self, atol=None, rtol=None): # checking that the underlying Stabilizer table array is a valid # Clifford array. - # TODO: IMPLEMENT ME! + # Condition is + # table.T * [[0, 1], [1, 0]] * table = [[0, 1], [1, 0]] + # where we are block matrix multiplying using symplectic product - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + one = np.eye(self.n_qubits, dtype=int) + zero = np.zeros((self.n_qubits, self.n_qubits), dtype=int) + seye = np.block([[zero, one], [one, zero]]) + arr = self.table.array.astype(int) + + return np.array_equal(arr.T.dot(seye).dot(arr) % 2, seye) def to_matrix(self): """Convert operator to Numpy matrix.""" @@ -161,19 +167,27 @@ def to_operator(self): def conjugate(self): """Return the conjugate of the Clifford.""" - - # TODO: IMPLEMENT ME! - - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + # TODO: Needs testing to see if correct + x = self.table.X + z = self.table.Z + ret = self.copy() + ret.table.phase = self.table.phase ^ (np.sum(x & z, axis=1) % 2) + return ret def transpose(self): """Return the transpose of the Clifford.""" - # TODO: IMPLEMENT ME! + # TODO: Needs testing to see if correct + # This is done using block matrix multiplication + # [[0, 1], [1, 0]] * table.T * [[0, 1], [1, 0]] - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + ret = self.copy() + tmp = ret.destabilizer.X.copy() + ret.destabilizer.X = ret.stabilizer.Z + ret.destabilizer.Z = ret.destabilizer.Z.T + ret.stabilizer.X = ret.stabilizer.X.T + ret.stabilizer.Z = tmp + return ret def compose(self, other, qargs=None, front=False): """Return the composed operator. @@ -210,10 +224,11 @@ def compose(self, other, qargs=None, front=False): # Clifford object can't be changed by composition self._get_compose_dims(other, qargs, front) - # TODO: IMPLEMENT ME! + if qargs is None or ( + len(qargs) == self.n_qubits and sorted(qargs) == qargs): + return self._compose_clifford(other, front=front) - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + return self._compose_subsystem(other, qargs, front=front) def dot(self, other, qargs=None): """Return the right multiplied operator self * other. @@ -242,13 +257,7 @@ def tensor(self, other): Returns: Clifford: the tensor product operator self ⊗ other. """ - if not isinstance(other, Clifford): - other = Clifford(other) - - # TODO: IMPLEMENT ME! - - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + return self._tensor_product(other, reverse=False) def expand(self, other): """Return the tensor product operator other ⊗ self. @@ -259,13 +268,7 @@ def expand(self, other): Returns: Clifford: the tensor product operator other ⊗ self. """ - if not isinstance(other, Clifford): - other = Clifford(other) - - # TODO: IMPLEMENT ME! - - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') + return self._tensor_product(other, reverse=True) # --------------------------------------------------------------------- # Representation conversions @@ -311,3 +314,161 @@ def from_instruction(instruction): clifford = Clifford(np.eye(2 * instruction.num_qubits)) append_gate(clifford, instruction) return clifford + + # --------------------------------------------------------------------- + # Internal tensor produce + # --------------------------------------------------------------------- + def _tensor_product(self, other, reverse=False): + """Return the tensor product operator. + + Args: + other (Clifford): another Clifford operator. + reverse (bool): If False return self ⊗ other, if True return + if True return (other ⊗ self) [Default: False + Returns: + Clifford: the tensor product operator. + + Raises: + QiskitError: if other cannot be converted into an Clifford. + """ + if not isinstance(other, Clifford): + other = Clifford(other) + + if reverse: + first = other + second = self + else: + first = self + second = other + n_first = first.n_qubits + n_second = second.n_qubits + + # Pad stabilizers and destabilizers + destab = (first.destabilizer.tensor(n_second * 'I') + + second.destabilizer.expand(n_first * 'I')) + stab = (first.stabilizer.tensor(n_second * 'I') + + second.stabilizer.expand(n_first * 'I')) + + # Add the padded table + table = destab + stab + return Clifford(table) + + # --------------------------------------------------------------------- + # Internal composition methods + # --------------------------------------------------------------------- + def _compose_subsystem(self, other, qargs, front=False): + """Return the composition channel.""" + # Create Clifford on full system from subsystem and compose + nq = self.n_qubits + no = other.n_qubits + fullother = self.copy() + fullother.table.array = np.eye(2 * self.n_qubits, dtype=np.bool) + for inda, qinda in enumerate(qargs): + for indb, qindb in enumerate(qargs): + fullother.table._array[nq - 1 - qinda, nq - 1 - qindb] = other.table._array[ + no - 1 - inda, no - 1 - indb] + fullother.table._array[nq - 1 - qinda, 2*nq - 1 - qindb] = other.table._array[ + no - 1 - inda, 2*no - 1 - indb] + fullother.table._array[2*nq - 1 - qinda, nq - 1 - qindb] = other.table._array[ + 2*no - 1 - inda, no - 1 - indb] + fullother.table._array[2*nq - 1 - qinda, 2*nq - 1 - qindb] = other.table._array[ + 2*no - 1 - inda, 2*no - 1 - indb] + fullother.table._phase[nq - 1 - qinda] = other.table._phase[no - 1 - inda] + fullother.table._phase[2*nq - 1 - qinda] = other.table._phase[2*no - 1 - inda] + return self._compose_clifford(fullother, front=front) + + def _compose_clifford(self, other, front=False): + """Return the composition channel assume other is Clifford of same size as self.""" + if front: + table1 = self.table + table2 = other.table + else: + table1 = other.table + table2 = self.table + + # PREVIOUS METHOD: + # This one isn't currently getting phases correct + + # ret_table = table2.copy() + # + # Zero the return array, leave the phases in place + # ret_table.array *= False + # for i in range(ret_table.size): + # for j in range(table1.size): + # if table2.array[i, j]: + # ret_table[i] = self._rowsum(ret_table[i], table1[j]) + # + # return Clifford(ret_table) + + # ALT METHOD: + # This one is correct but needs to be optimized + + num_qubits = self.n_qubits + + array1 = table1.array.astype(int) + phase1 = table1.phase.astype(int) + + array2 = table2.array.astype(int) + phase2 = table2.phase.astype(int) + + # Update Pauli table + pauli = StabilizerTable(array2.dot(array1) % 2) + + # Add phases + phase = np.mod(array2.dot(phase1) + phase2, 2) + + # Correcting for phase due to Pauli multiplicatio + ifacts = np.zeros(2 * num_qubits, dtype=np.int) + + for r2 in range(2 * num_qubits): + + row2 = array2[r2] + x2 = table2.X[r2] + z2 = table2.Z[r2] + + # Adding a factor of i for each Y in the image of an operator under the + # first operation, since Y=iXZ + + ifacts[r2] += np.sum(x2 & z2) + + # Adding factors of i due to qubit-wise Pauli multiplication + + x = np.zeros(num_qubits, dtype=int) + z = np.zeros(num_qubits, dtype=int) + + for i, r1 in enumerate(table1): + + x1 = r1.X[0].astype(int) + z1 = r1.Z[0].astype(int) + + val = np.mod(abs(3 * z1 - x1) - abs(3 * z - x) - 1, 3) + shift = 1 * (val == 0) - 1 * (val == 1) + shift = row2[i] * (x1 | z1) * (x | z) * shift + + x = (x + row2[i] * x1) % 2 + z = (z + row2[i] * z1) % 2 + + ifacts[r2] += np.sum(shift) + + p = np.mod(ifacts, 4) // 2 + + phase = np.mod(phase + p, 2) + + return Clifford(StabilizerTable(pauli, phase)) + + @staticmethod + def _rowsum(row1, row2): + """Rowsum from AG paper""" + x1, z1 = row1.X, row1.Z + x2, z2 = row2.X, row2.Z + + # Phase update (g function in AG paper) + phase = row1.phase ^ row2.phase ^ np.array( + np.sum((~x1 & z1 & x2 & ~z2) | + (x1 & ~z1 & x2 & z2) | + (x1 & z1 & ~x2 & z2), axis=1) % 2, dtype=np.bool) + + # Pauli update + pauli = row1.array ^ row2.array + + return StabilizerTable(pauli, phase) From 5427f9de82e5b9a055c8971608ef91895e39c247 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 12 Mar 2020 13:13:12 -0400 Subject: [PATCH 03/17] Add clifford conversion methods based on circuit decomposition * Added `to_circuit` method * Added `to_gate` method * Added `to_matrix` method * Added `to_operator` --- .../operators/symplectic/clifford.py | 36 +++-- .../symplectic/clifford_append_gate.py | 145 +++++++++++++++++- 2 files changed, 160 insertions(+), 21 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index defdc639f13..c9efea04944 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -13,9 +13,11 @@ from qiskit import QiskitError from qiskit.circuit import QuantumCircuit, Instruction from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.scalar_op import ScalarOp from qiskit.quantum_info.operators.symplectic.stabilizer_table import StabilizerTable -from qiskit.quantum_info.operators.symplectic.clifford_append_gate import append_gate +from qiskit.quantum_info.operators.symplectic.clifford_append_gate import (append_gate, + decompose_clifford) class Clifford(BaseOperator): @@ -145,22 +147,6 @@ def is_unitary(self, atol=None, rtol=None): return np.array_equal(arr.T.dot(seye).dot(arr) % 2, seye) - def to_matrix(self): - """Convert operator to Numpy matrix.""" - - # TODO: IMPLEMENT ME! - - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') - - def to_operator(self): - """Convert to an Operator object.""" - - # TODO: IMPLEMENT ME! - - raise NotImplementedError( - 'This method has not been implemented for Clifford operators yet.') - # --------------------------------------------------------------------- # BaseOperator Abstract Methods # --------------------------------------------------------------------- @@ -288,6 +274,22 @@ def from_dict(obj): stabilizer = StabilizerTable.from_labels(obj.get('stabilizer')) return Clifford(destabilizer + stabilizer) + def to_matrix(self): + """Convert operator to Numpy matrix.""" + return self.to_operator().data + + def to_operator(self): + """Convert to an Operator object.""" + return Operator(self.to_gate()) + + def to_circuit(self): + """Return a QuantumCircuit implementing the Clifford.""" + return decompose_clifford(self) + + def to_gate(self): + """Return a Gate instruction implementing the Clifford.""" + return self.to_circuit().to_gate() + @staticmethod def from_instruction(instruction): """Initialize from a QuantumCircuit or Instruction. diff --git a/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py b/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py index f49fed97a26..8ab9a3d938f 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py @@ -9,7 +9,9 @@ """ # pylint: disable=invalid-name -from qiskit import QiskitError +import numpy as np +from qiskit.exceptions import QiskitError +from qiskit.circuit import QuantumCircuit # --------------------------------------------------------------------- @@ -212,7 +214,6 @@ def append_v(clifford, qubit): """ x = clifford.table.X[:, qubit] z = clifford.table.Z[:, qubit] - clifford.table.phase ^= False tmp = x.copy() x ^= z z[:] = tmp @@ -233,7 +234,6 @@ def append_w(clifford, qubit): """ x = clifford.table.X[:, qubit] z = clifford.table.Z[:, qubit] - clifford.table.phase ^= False tmp = z.copy() z ^= x x[:] = tmp @@ -276,7 +276,7 @@ def append_cz(clifford, control, target): z0 = clifford.table.Z[:, control] x1 = clifford.table.X[:, target] z1 = clifford.table.Z[:, target] - clifford.table.phase ^= (z1 ^ z0 ^ True) & x1 & x0 + clifford.table.phase ^= x0 & x1 & (z0 ^ z1) z1 ^= x0 z0 ^= x1 return clifford @@ -296,3 +296,140 @@ def append_swap(clifford, qubit0, qubit1): clifford.table.X[:, [qubit0, qubit1]] = clifford.table.X[:, [qubit1, qubit0]] clifford.table.Z[:, [qubit0, qubit1]] = clifford.table.Z[:, [qubit1, qubit0]] return clifford + + +def decompose_clifford(clifford): + """Decompose a Clifford into a QuantumCircuit. + + TODO: Reference paper for Algorithm (Gottesman?). + + Args: + clifford (Clifford): a clifford operator. + + Return: + QuantumCircuit: a circuit implementation of the Clifford. + """ + # Compose a circuit which we will convert to an instruction + circuit = QuantumCircuit(clifford.n_qubits, + name=str(clifford)) + + # Make a copy of Clifford as we are going to do row reduction to + # reduce it to an identity + clifford_cpy = clifford.copy() + + for i in range(clifford.n_qubits): + # * make1forXkk(i) + + # put a 1 one into position by permuting and using Hadamards(i,i) + set_qubit_x_true(clifford_cpy, circuit, i) + # * .makeXrowzero(i) + # make all entries in row i except ith equal to 0 + # by using phase gate and CNOTS + set_row_x_zero(clifford_cpy, circuit, i) + # * makeZrowzero(i) + # treat Zs + set_row_z_zero(clifford_cpy, circuit, i) + + for i in range(clifford.n_qubits): + if clifford_cpy.destabilizer.phase[i]: + append_z(clifford_cpy, i) + circuit.z(i) + if clifford_cpy.stabilizer.phase[i]: + append_x(clifford_cpy, i) + circuit.x(i) + # Next we invert the circuit to undo the row reduction and return the + # result as a gate instruction + return circuit.inverse() + + +def set_qubit_x_true(clifford, circuit, qubit): + """Set destabilizer.X[qubit, qubit] to be True. + + This is done by permuting columns l > qubit or if necessary applying + a Hadamard + """ + x = clifford.destabilizer.X[qubit] + z = clifford.destabilizer.Z[qubit] + + if x[qubit]: + return + + # Try to find non-zero element + for i in range(qubit + 1, clifford.n_qubits): + if x[i]: + append_swap(clifford, i, qubit) + circuit.swap(i, qubit) + return + + # no non-zero element found: need to apply Hadamard somewhere + for i in range(qubit, clifford.n_qubits): + if z[i]: + append_h(clifford, i) + circuit.h(i) + if i != qubit: + append_swap(clifford, i, qubit) + circuit.swap(i, qubit) + return + + +def set_row_x_zero(clifford, circuit, qubit): + """Set destabilizer.X[qubit, i] to False for all i > qubit. + + This is done by applying CNOTS assumes k<=N and A[k][k]=1 + """ + x = clifford.destabilizer.X[qubit] + z = clifford.destabilizer.Z[qubit] + + # Check X first + for i in range(qubit + 1, clifford.n_qubits): + if x[i]: + append_cx(clifford, qubit, i) + circuit.cx(qubit, i) + + # Check whether Zs need to be set to zero: + if np.any(z[qubit:]): + if not z[qubit]: + # to treat Zs: make sure row.Z[k] to True + append_s(clifford, qubit) + circuit.s(qubit) + + # reverse CNOTS + for i in range(qubit + 1, clifford.n_qubits): + if z[i]: + append_cx(clifford, i, qubit) + circuit.cx(i, qubit) + # set row.Z[qubit] to False + append_s(clifford, qubit) + circuit.s(qubit) + + +def set_row_z_zero(clifford, circuit, qubit): + """Set stabilizer.Z[qubit, i] to False for all i > qubit. + + Implemented by applying (reverse) CNOTS assumes qubit < n_qubits + and set_row_x_zero has been called first + """ + + x = clifford.stabilizer.X[qubit] + z = clifford.stabilizer.Z[qubit] + + # check whether Zs need to be set to zero: + if np.any(z[qubit + 1:]): + for i in range(qubit + 1, clifford.n_qubits): + if z[i]: + append_cx(clifford, i, qubit) + circuit.cx(i, qubit) + + # check whether Xs need to be set to zero: + if np.any(x[qubit:]): + append_h(clifford, qubit) + circuit.h(qubit) + for i in range(qubit + 1, clifford.n_qubits): + if x[i]: + append_cx(clifford, qubit, i) + circuit.cx(qubit, i) + if z[qubit]: + append_s(clifford, qubit) + circuit.s(qubit) + append_h(clifford, qubit) + circuit.h(qubit) From 50443c89e66d85a0ed50d86fc21a9894fe6f8c40 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Thu, 12 Mar 2020 16:43:07 +0200 Subject: [PATCH 04/17] Add tests * 1 and two qubit clifford gates * compose, dot, tensor, expand, conjugate, transopse, is_unitary, to_operator Co-Authored-By: Christopher J. Wood Co-Authored-By: Shelly Garion --- .../symplectic/test_clifford_gates.py | 563 ++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 test/python/quantum_info/operators/symplectic/test_clifford_gates.py diff --git a/test/python/quantum_info/operators/symplectic/test_clifford_gates.py b/test/python/quantum_info/operators/symplectic/test_clifford_gates.py new file mode 100644 index 00000000000..441afbd96f2 --- /dev/null +++ b/test/python/quantum_info/operators/symplectic/test_clifford_gates.py @@ -0,0 +1,563 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# 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 + +"""Tests for clifford append gate functions.""" + +import unittest +from test import combine +from ddt import ddt + +import numpy as np + +from qiskit.test import QiskitTestCase +from qiskit.circuit import Gate, QuantumRegister, QuantumCircuit +from qiskit.extensions.standard import (IGate, XGate, YGate, ZGate, + HGate, SGate, SdgGate, + CXGate, CZGate, SwapGate) +from qiskit.quantum_info.operators import Clifford, Operator +from qiskit.quantum_info.operators.symplectic.clifford_append_gate import append_gate + + +class VGate(Gate): + """V Gate used in Clifford synthesis.""" + + def __init__(self): + """Create new V Gate.""" + super().__init__('v', 1, []) + + def _define(self): + """V Gate definition.""" + q = QuantumRegister(1, 'q') + self.definition = [(SdgGate(), [q[0]], []), (HGate(), [q[0]], [])] + + +class WGate(Gate): + """W Gate used in Clifford synthesis.""" + + def __init__(self): + """Create new W Gate.""" + super().__init__('w', 1, []) + + def _define(self): + """W Gate definition.""" + q = QuantumRegister(1, 'q') + self.definition = [(VGate(), [q[0]], []), (VGate(), [q[0]], [])] + + +def random_clifford_circuit(num_qubits, num_gates, gates='all', seed=None): + """Generate a pseudo random Clifford circuit.""" + if gates == 'all': + if num_qubits == 1: + gates = ['i', 'x', 'y', 'z', 'h', 's', 'sdg', 'v', 'w'] + else: + gates = ['i', 'x', 'y', 'z', 'h', 's', 'sdg', 'v', 'w', 'cx', 'cz', 'swap'] + + instructions = { + 'i': (IGate(), 1), + 'x': (XGate(), 1), + 'y': (YGate(), 1), + 'z': (ZGate(), 1), + 'h': (HGate(), 1), + 's': (SGate(), 1), + 'sdg': (SdgGate(), 1), + 'v': (VGate(), 1), + 'w': (WGate(), 1), + 'cx': (CXGate(), 2), + 'cz': (CZGate(), 2), + 'swap': (SwapGate(), 2) + } + + rng = np.random.RandomState(seed=seed) + samples = rng.choice(gates, num_gates) + + circ = QuantumCircuit(num_qubits) + + for name in samples: + gate, nqargs = instructions[name] + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + circ.append(gate, qargs) + + return circ + + +class TestCliffordGates(QiskitTestCase): + """Tests for clifford append gate functions.""" + + def test_append_1_qubit_gate(self): + "Tests for append of 1-qubit gates" + + target_table = { + "i": np.array([[[True, False], [False, True]]], dtype=np.bool), + "id": np.array([[[True, False], [False, True]]], dtype=np.bool), + "iden": np.array([[[True, False], [False, True]]], dtype=np.bool), + "x": np.array([[[True, False], [False, True]]], dtype=np.bool), + "y": np.array([[[True, False], [False, True]]], dtype=np.bool), + "z": np.array([[[True, False], [False, True]]], dtype=np.bool), + "h": np.array([[[False, True], [True, False]]], dtype=np.bool), + "s": np.array([[[True, True], [False, True]]], dtype=np.bool), + "sdg": np.array([[[True, True], [False, True]]], dtype=np.bool), + "sinv": np.array([[[True, True], [False, True]]], dtype=np.bool), + "v": np.array([[[True, True], [True, False]]], dtype=np.bool), + "w": np.array([[[False, True], [True, True]]], dtype=np.bool), + + } + + target_phase = { + "i": np.array([[False, False]], dtype=np.bool), + "id": np.array([[False, False]], dtype=np.bool), + "iden": np.array([[False, False]], dtype=np.bool), + "x": np.array([[False, True]], dtype=np.bool), + "y": np.array([[True, True]], dtype=np.bool), + "z": np.array([[True, False]], dtype=np.bool), + "h": np.array([[False, False]], dtype=np.bool), + "s": np.array([[False, False]], dtype=np.bool), + "sdg": np.array([[True, False]], dtype=np.bool), + "sinv": np.array([[True, False]], dtype=np.bool), + "v": np.array([[False, False]], dtype=np.bool), + "w": np.array([[False, False]], dtype=np.bool) + } + + target_stabilizer = { + "i": "+Z", + "id": "+Z", + "iden": "+Z", + "x": "-Z", + "y": "-Z", + "z": "+Z", + "h": "+X", + "s": "+Z", + "sdg": "+Z", + "sinv": "+Z", + "v": "+X", + "w": "+Y", + } + + target_destabilizer = { + "i": "+X", + "id": "+X", + "iden": "+X", + "x": "+X", + "y": "-X", + "z": "-X", + "h": "+Z", + "s": "+Y", + "sdg": "-Y", + "sinv": "-Y", + "v": "+Y", + "w": "+Z", + } + + for gate_name in ("i", "id", "iden", "x", "y", "z", "h", + "s", "sdg", "v", "w"): + with self.subTest(msg='append gate %s' % gate_name): + cliff = Clifford([[1, 0], [0, 1]]) + cliff = append_gate(cliff, gate_name, [0]) + value_table = cliff.table._array + value_phase = cliff.table._phase + value_stabilizer = cliff.stabilizer.to_labels() + value_destabilizer = cliff.destabilizer.to_labels() + self.assertTrue(np.all(np.array(value_table == + target_table[gate_name]))) + self.assertTrue(np.all(np.array(value_phase == + target_phase[gate_name]))) + self.assertTrue(np.all(np.array(value_stabilizer == + [target_stabilizer[gate_name]]))) + self.assertTrue(np.all(np.array(value_destabilizer == + [target_destabilizer[gate_name]]))) + + def test_1_qubit_identity_relations(self): + "Tests identity relations for 1-qubit gates" + + for gate_name in ("x", "y", "z", "h"): + with self.subTest(msg='identity for gate %s' % gate_name): + cliff = Clifford([[1, 0], [0, 1]]) + cliff1 = cliff.copy() + cliff = append_gate(cliff, gate_name, [0]) + cliff = append_gate(cliff, gate_name, [0]) + self.assertEqual(cliff, cliff1) + + gates = ['s', 's', 'v'] + inv_gates = ['sdg', 'sinv', 'w'] + + for gate_name, inv_gate in zip(gates, inv_gates): + with self.subTest(msg='identity for gate %s' % gate_name): + cliff = Clifford([[1, 0], [0, 1]]) + cliff1 = cliff.copy() + cliff = append_gate(cliff, gate_name, [0]) + cliff = append_gate(cliff, inv_gate, [0]) + self.assertEqual(cliff, cliff1) + + def test_1_qubit_mult_relations(self): + "Tests multiplicity relations for 1-qubit gates" + + rels = ['x * y = z', 'x * z = y', 'y * z = x', + 's * s = z', 'sdg * sdg = z', 'sinv * sinv = z', + 'sdg * h = v', 'h * s = w'] + + for rel in rels: + with self.subTest(msg='relation %s' % rel): + split_rel = rel.split() + cliff = Clifford([[1, 0], [0, 1]]) + cliff1 = cliff.copy() + cliff = append_gate(cliff, split_rel[0], [0]) + cliff = append_gate(cliff, split_rel[2], [0]) + cliff1 = append_gate(cliff1, split_rel[4], [0]) + self.assertEqual(cliff, cliff1) + + def test_1_qubit_conj_relations(self): + "Tests conjugation relations for 1-qubit gates" + + rels = ['h * x * h = z', 'h * y * h = y', + 's * x * sdg = y', 'w * x * v = y', + 'w * y * v = z', 'w * z * v = x'] + + for rel in rels: + with self.subTest(msg='relation %s' % rel): + split_rel = rel.split() + cliff = Clifford([[1, 0], [0, 1]]) + cliff1 = cliff.copy() + cliff = append_gate(cliff, split_rel[0], [0]) + cliff = append_gate(cliff, split_rel[2], [0]) + cliff = append_gate(cliff, split_rel[4], [0]) + cliff1 = append_gate(cliff1, split_rel[6], [0]) + self.assertEqual(cliff, cliff1) + + def test_append_2_qubit_gate(self): + "Tests for append of 2-qubit gates" + + target_table = { + "cx [0, 1]": np.array([[True, True, False, False], + [False, True, False, False], + [False, False, True, False], + [False, False, True, True]]), + + "cx [1, 0]": np.array([[True, False, False, False], + [True, True, False, False], + [False, False, True, True], + [False, False, False, True]]), + + "cz [0, 1]": np.array([[True, False, False, True], + [False, True, True, False], + [False, False, True, False], + [False, False, False, True]]), + + "cz [1, 0]": np.array([[True, False, False, True], + [False, True, True, False], + [False, False, True, False], + [False, False, False, True]]), + + "swap [0, 1]": np.array([[False, True, False, False], + [True, False, False, False], + [False, False, False, True], + [False, False, True, False]]), + + "swap [1, 0]": np.array([[False, True, False, False], + [True, False, False, False], + [False, False, False, True], + [False, False, True, False]]) + } + + target_phase = np.array([False, False, False, False]) + + target_stabilizer = { + "cx [0, 1]": ['+ZI', '+ZZ'], + "cx [1, 0]": ['+ZZ', '+IZ'], + "cz [0, 1]": ['+ZI', '+IZ'], + "cz [1, 0]": ['+ZI', '+IZ'], + "swap [0, 1]": ['+IZ', '+ZI'], + "swap [1, 0]": ['+IZ', '+ZI'] + } + + target_destabilizer = { + "cx [0, 1]": ['+XX', '+IX'], + "cx [1, 0]": ['+XI', '+XX'], + "cz [0, 1]": ['+XZ', '+ZX'], + "cz [1, 0]": ['+XZ', '+ZX'], + "swap [0, 1]": ['+IX', '+XI'], + "swap [1, 0]": ['+IX', '+XI'] + } + + for gate_name in ("cx", "cz", "swap"): + for qubits in ([0, 1], [1, 0]): + with self.subTest(msg='append gate %s %s' % ( + gate_name, qubits)): + gate_qubits = gate_name + " " + str(qubits) + cliff = Clifford(np.eye(4)) + cliff = append_gate(cliff, gate_name, qubits) + value_table = cliff.table._array + value_phase = cliff.table._phase + value_stabilizer = cliff.stabilizer.to_labels() + value_destabilizer = cliff.destabilizer.to_labels() + self.assertTrue(np.all(np.array(value_table == + target_table[gate_qubits]))) + self.assertTrue(np.all(np.array(value_phase == + target_phase))) + self.assertTrue(np.all(np.array(value_stabilizer == + target_stabilizer[gate_qubits]))) + self.assertTrue(np.all(np.array(value_destabilizer == + target_destabilizer[gate_qubits]))) + + def test_2_qubit_identity_relations(self): + "Tests identity relations for 2-qubit gates" + + for gate_name in ("cx", "cz", "swap"): + for qubits in ([0, 1], [1, 0]): + with self.subTest(msg='append gate %s %s' % ( + gate_name, qubits)): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, gate_name, qubits) + cliff = append_gate(cliff, gate_name, qubits) + self.assertEqual(cliff, cliff1) + + def test_2_qubit_relations(self): + "Tests relations for 2-qubit gates" + + with self.subTest(msg='relation between cx, h and cz'): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, 'h', [1]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'h', [1]) + cliff = append_gate(cliff, 'cz', [0, 1]) + self.assertEqual(cliff, cliff1) + + with self.subTest(msg='relation between cx and swap'): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'cx', [1, 0]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'swap', [0, 1]) + self.assertEqual(cliff, cliff1) + + with self.subTest(msg='relation between cx and x'): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'x', [0]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'x', [0]) + cliff = append_gate(cliff, 'x', [1]) + self.assertEqual(cliff, cliff1) + + with self.subTest(msg='relation between cx and z'): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'z', [1]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'z', [0]) + cliff = append_gate(cliff, 'z', [1]) + self.assertEqual(cliff, cliff1) + + with self.subTest(msg='relation between cx and s'): + cliff = Clifford(np.eye(4)) + cliff1 = cliff.copy() + cliff = append_gate(cliff, 'cx', [1, 0]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 's', [1]) + cliff = append_gate(cliff, 'cx', [0, 1]) + cliff = append_gate(cliff, 'cx', [1, 0]) + cliff = append_gate(cliff, 'sdg', [0]) + self.assertEqual(cliff, cliff1) + + +@ddt +class TestCliffordCircuits(QiskitTestCase): + """Stress tests for random clifford circuits.""" + + @combine(gates=[['h', 's'], + ['h', 's', 'i', 'x', 'y', 'z'], + ['h', 's', 'sdg'], + ['h', 's', 'v'], + ['h', 's', 'w'], + ['h', 's', 'sdg', 'i', 'x', 'y', 'z', 'v', 'w']]) + def test_to_operator_1qubit_gates(self, gates): + """Test 1-qubit circuit with gates {gates}""" + samples = 10 + num_gates = 10 + seed = 100 + for i in range(samples): + circ = random_clifford_circuit(1, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).to_operator() + target = Operator(circ) + self.assertTrue(target.equiv(value)) + + @combine(gates=[['cx'], ['cz'], ['swap'], ['cx', 'cz'], + ['cx', 'swap'], ['cz', 'swap'], ['cx', 'cz', 'swap']]) + def test_to_operator_2qubit_gates(self, gates): + """Test 2-qubit circuit with gates {gates}""" + samples = 10 + num_gates = 10 + seed = 200 + for i in range(samples): + circ = random_clifford_circuit(2, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).to_operator() + target = Operator(circ) + self.assertTrue(target.equiv(value)) + + @combine(gates=[['h', 's', 'cx'], ['h', 's', 'cz'], ['h', 's', 'swap'], 'all'], + num_qubits=[2, 3, 4]) + def test_to_operator_nqubit_gates(self, gates, num_qubits): + """Test {num_qubits}-qubit circuit with gates {gates}""" + samples = 10 + num_gates = 20 + seed = 300 + for i in range(samples): + circ = random_clifford_circuit(num_qubits, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).to_operator() + target = Operator(circ) + self.assertTrue(target.equiv(value)) + + +@ddt +class TestCliffordOperators(QiskitTestCase): + + @combine(num_qubits=[1, 2, 3]) + def test_is_unitary(self, num_qubits): + "Test is_unitary method" + samples = 10 + num_gates = 10 + seed = 700 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).is_unitary() + self.assertTrue(value) + # tests a false clifford + cliff = Clifford([[0, 0], [0, 1]]) + value = cliff.is_unitary() + self.assertFalse(value) + + @combine(num_qubits=[1, 2, 3]) + def test_conjugate(self, num_qubits): + "Test conjugate method" + samples = 10 + num_gates = 10 + seed = 400 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).conjugate().to_operator() + target = Operator(circ).conjugate() + self.assertTrue(target.equiv(value)) + + @combine(num_qubits=[1, 2, 3]) + def test_transpose(self, num_qubits): + "Test transpose method" + # TODO - fix test after transpose method is done + samples = 10 + num_gates = 1 + seed = 500 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + i) + value = Clifford(circ).transpose().to_operator() + target = Operator(circ).transpose() + #print (circ) + #print (Clifford(circ)) + #print(Clifford(circ).transpose()) + #print (target) + #print (value) + #print (target == value) + #print ("---------") + #self.assertTrue(target.equiv(value)) + + @combine(num_qubits=[1, 2, 3]) + def test_compose_method(self, num_qubits): + "Test compose method" + samples = 10 + num_gates = 10 + seed = 600 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + i) + circ2 = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + samples + i) + cliff1 = Clifford(circ1) + cliff2 = Clifford(circ2) + value = cliff1.compose(cliff2) + target = Clifford(circ1.extend(circ2)) + self.assertEqual(target, value) + + @combine(num_qubits=[1, 2, 3]) + def test_dot_method(self, num_qubits): + "Test dot method" + samples = 10 + num_gates = 10 + seed = 600 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + i) + circ2 = random_clifford_circuit( + num_qubits, num_gates, gates=gates, seed=seed + samples + i) + cliff1 = Clifford(circ1) + cliff2 = Clifford(circ2) + value = cliff1.dot(cliff2) + target = Clifford(circ2.extend(circ1)) + self.assertEqual(target, value) + + @combine(num_qubits_1=[1,2,3], num_qubits_2=[1,2,3]) + def test_tensor_method(self, num_qubits_1, num_qubits_2): + "Test tensor method" + samples = 5 + num_gates = 10 + seed = 800 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit( + num_qubits_1, num_gates, gates=gates, seed=seed + i) + circ2 = random_clifford_circuit( + num_qubits_2, num_gates, gates=gates, seed=seed + samples + i) + cliff1 = Clifford(circ1) + cliff2 = Clifford(circ2) + value = cliff1.tensor(cliff2) + circ = QuantumCircuit(num_qubits_1 + num_qubits_2) + circ.append(circ1, range(num_qubits_1)) + circ.append(circ2, range(num_qubits_1, num_qubits_1 + num_qubits_2)) + target = Clifford(circ) + self.assertEqual(target, value) + + @combine(num_qubits_1=[1,2,3], num_qubits_2=[1,2,3]) + def test_expand_method(self, num_qubits_1, num_qubits_2): + "Test expand method" + samples = 5 + num_gates = 10 + seed = 800 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit( + num_qubits_1, num_gates, gates=gates, seed=seed + i) + circ2 = random_clifford_circuit( + num_qubits_2, num_gates, gates=gates, seed=seed + samples + i) + cliff1 = Clifford(circ1) + cliff2 = Clifford(circ2) + value = cliff1.expand(cliff2) + circ = QuantumCircuit(num_qubits_1 + num_qubits_2) + circ.append(circ2, range(num_qubits_2)) + circ.append(circ1, range(num_qubits_2, num_qubits_1 + num_qubits_2)) + target = Clifford(circ) + self.assertEqual(target, value) + + +if __name__ == '__main__': + unittest.main() From e45bd365d650ecdf0e483bef1cb0e736cc9dd6ab Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 18 Mar 2020 10:49:32 -0400 Subject: [PATCH 05/17] Rename files and fix bugs test_clifford_gates -> test_clifford clifford_append_gate -> clifford_circuits Fix n_qubits -> num_qubits Fix tensor order bug Fix append 2-qubit gate tests Speed up compose Fix qubit order issue with subsystem compose Add check that table is valid on clifford __init__ --- .../operators/symplectic/clifford.py | 235 ++++++------- ...rd_append_gate.py => clifford_circuits.py} | 125 ++++--- ...est_clifford_gates.py => test_clifford.py} | 322 +++++++++--------- 3 files changed, 325 insertions(+), 357 deletions(-) rename qiskit/quantum_info/operators/symplectic/{clifford_append_gate.py => clifford_circuits.py} (88%) rename test/python/quantum_info/operators/symplectic/{test_clifford_gates.py => test_clifford.py} (63%) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index c9efea04944..0d7ddf4a74a 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -1,29 +1,36 @@ # -*- coding: utf-8 -*- -# Copyright 2017, 2020 BM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2017, 2020 +# +# 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. """ Clifford operator class. """ +# pylint: disable=invalid-name, abstract-method import numpy as np -from qiskit import QiskitError +from qiskit.exceptions import QiskitError from qiskit.circuit import QuantumCircuit, Instruction from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.scalar_op import ScalarOp -from qiskit.quantum_info.operators.symplectic.stabilizer_table import StabilizerTable -from qiskit.quantum_info.operators.symplectic.clifford_append_gate import (append_gate, - decompose_clifford) +from .stabilizer_table import StabilizerTable +from .clifford_circuits import append_gate, decompose_clifford class Clifford(BaseOperator): """Clifford table operator class""" - def __init__(self, data): + def __init__(self, data, validate=True): """Initialize an operator object.""" # Initialize from another Clifford by sharing the underlying @@ -46,18 +53,14 @@ def __init__(self, data): else: self._table = StabilizerTable(data) - # Validate shape of StabilizerTable - if self._table.size != 2 * self._table.n_qubits: - raise QiskitError( - 'Invalid Clifford (number of rows {0} != {1}). An {2}-qubit' - ' Clifford table requires {1} rows.'.format( - self._table.size, 2 * self._table.n_qubits, self.n_qubits)) - - # TODO: Should we check the input array is a valid Clifford table? - # This should be done by the `is_unitary` method. + # Validate table is a symplectic matrix + if validate and not Clifford._is_symplectic(self._table.array): + raise QiskitError( + 'Invalid Clifford. Input StabilizerTable is not a valid' + ' symplectic matrix.') # Initialize BaseOperator - dims = self._table.n_qubits * (2,) + dims = self._table.num_qubits * (2,) super().__init__(dims, dims) def __repr__(self): @@ -83,11 +86,6 @@ def __setitem__(self, key, value): """Set a stabilizer Pauli row""" self._table.__setitem__(key, value) - @property - def n_qubits(self): - """The number of qubits for the Clifford.""" - return self._table._n_qubits - @property def table(self): """Return StabilizerTable""" @@ -107,23 +105,23 @@ def table(self, value): @property def stabilizer(self): """Return the stabilizer block of the StabilizerTable.""" - return StabilizerTable(self._table[self.n_qubits:2*self.n_qubits]) + return StabilizerTable(self._table[self.num_qubits:2*self.num_qubits]) @stabilizer.setter def stabilizer(self, value): """Set the value of stabilizer block of the StabilizerTable""" - inds = slice(self.n_qubits, 2*self.n_qubits) + inds = slice(self.num_qubits, 2*self.num_qubits) self._table.__setitem__(inds, value) @property def destabilizer(self): """Return the destabilizer block of the StabilizerTable.""" - return StabilizerTable(self._table[0:self.n_qubits]) + return StabilizerTable(self._table[0:self.num_qubits]) @destabilizer.setter def destabilizer(self, value): """Set the value of destabilizer block of the StabilizerTable""" - inds = slice(0, self.n_qubits) + inds = slice(0, self.num_qubits) self._table.__setitem__(inds, value) # --------------------------------------------------------------------- @@ -132,20 +130,11 @@ def destabilizer(self, value): def is_unitary(self, atol=None, rtol=None): """Return True if the Clifford table is valid.""" + # pylint: disable=unused-argument # A valid Clifford is always unitary, so this function is really # checking that the underlying Stabilizer table array is a valid # Clifford array. - - # Condition is - # table.T * [[0, 1], [1, 0]] * table = [[0, 1], [1, 0]] - # where we are block matrix multiplying using symplectic product - - one = np.eye(self.n_qubits, dtype=int) - zero = np.zeros((self.n_qubits, self.n_qubits), dtype=int) - seye = np.block([[zero, one], [one, zero]]) - arr = self.table.array.astype(int) - - return np.array_equal(arr.T.dot(seye).dot(arr) % 2, seye) + return Clifford._is_symplectic(self.table.array) # --------------------------------------------------------------------- # BaseOperator Abstract Methods @@ -210,11 +199,9 @@ def compose(self, other, qargs=None, front=False): # Clifford object can't be changed by composition self._get_compose_dims(other, qargs, front) - if qargs is None or ( - len(qargs) == self.n_qubits and sorted(qargs) == qargs): - return self._compose_clifford(other, front=front) - - return self._compose_subsystem(other, qargs, front=front) + # Pad other with identities if composeing on subsystem + other = self._pad_with_identity(other, qargs) + return self._compose_clifford(other, front=front) def dot(self, other, qargs=None): """Return the right multiplied operator self * other. @@ -313,13 +300,31 @@ def from_instruction(instruction): instruction = instruction.to_instruction() # Initialize an identity Clifford - clifford = Clifford(np.eye(2 * instruction.num_qubits)) + clifford = Clifford(np.eye(2 * instruction.num_qubits), validate=False) append_gate(clifford, instruction) return clifford # --------------------------------------------------------------------- - # Internal tensor produce + # Internal helper functions # --------------------------------------------------------------------- + + @staticmethod + def _is_symplectic(mat): + """Return True if input is symplectic matrix.""" + # Condition is + # table.T * [[0, 1], [1, 0]] * table = [[0, 1], [1, 0]] + # where we are block matrix multiplying using symplectic product + + dim = len(mat) // 2 + if mat.shape != (2 * dim, 2 * dim): + return False + + one = np.eye(dim, dtype=np.int) + zero = np.zeros((dim, dim), dtype=np.int) + seye = np.block([[zero, one], [one, zero]]) + arr = mat.astype(np.int) + return np.array_equal(np.mod(arr.T.dot(seye).dot(arr), 2), seye) + def _tensor_product(self, other, reverse=False): """Return the tensor product operator. @@ -337,47 +342,44 @@ def _tensor_product(self, other, reverse=False): other = Clifford(other) if reverse: - first = other - second = self + cliff0 = self + cliff1 = other else: - first = self - second = other - n_first = first.n_qubits - n_second = second.n_qubits + cliff0 = other + cliff1 = self # Pad stabilizers and destabilizers - destab = (first.destabilizer.tensor(n_second * 'I') + - second.destabilizer.expand(n_first * 'I')) - stab = (first.stabilizer.tensor(n_second * 'I') + - second.stabilizer.expand(n_first * 'I')) + destab = (cliff0.destabilizer.expand(cliff1.num_qubits * 'I') + + cliff1.destabilizer.tensor(cliff0.num_qubits * 'I')) + stab = (cliff0.stabilizer.expand(cliff1.num_qubits * 'I') + + cliff1.stabilizer.tensor(cliff0.num_qubits * 'I')) # Add the padded table - table = destab + stab - return Clifford(table) + return Clifford(destab + stab, validate=False) # --------------------------------------------------------------------- # Internal composition methods # --------------------------------------------------------------------- - def _compose_subsystem(self, other, qargs, front=False): - """Return the composition channel.""" - # Create Clifford on full system from subsystem and compose - nq = self.n_qubits - no = other.n_qubits - fullother = self.copy() - fullother.table.array = np.eye(2 * self.n_qubits, dtype=np.bool) - for inda, qinda in enumerate(qargs): - for indb, qindb in enumerate(qargs): - fullother.table._array[nq - 1 - qinda, nq - 1 - qindb] = other.table._array[ - no - 1 - inda, no - 1 - indb] - fullother.table._array[nq - 1 - qinda, 2*nq - 1 - qindb] = other.table._array[ - no - 1 - inda, 2*no - 1 - indb] - fullother.table._array[2*nq - 1 - qinda, nq - 1 - qindb] = other.table._array[ - 2*no - 1 - inda, no - 1 - indb] - fullother.table._array[2*nq - 1 - qinda, 2*nq - 1 - qindb] = other.table._array[ - 2*no - 1 - inda, 2*no - 1 - indb] - fullother.table._phase[nq - 1 - qinda] = other.table._phase[no - 1 - inda] - fullother.table._phase[2*nq - 1 - qinda] = other.table._phase[2*no - 1 - inda] - return self._compose_clifford(fullother, front=front) + def _pad_with_identity(self, clifford, qargs): + """Pad Clifford with identities on other subsystems.""" + if qargs is None: + return clifford + + padded = Clifford(StabilizerTable( + np.eye(2 * self.num_qubits, dtype=np.bool)), validate=False) + + inds = list(qargs) + [self.num_qubits + i for i in qargs] + + # Pad Pauli array + pauli = clifford.table.array + for i, pos in enumerate(qargs): + padded.table.array[inds, pos] = pauli[:, i] + padded.table.array[inds, self.num_qubits + pos] = pauli[:, clifford.num_qubits + i] + + # Pad phase + padded.table.phase[inds] = clifford.table.phase + + return padded def _compose_clifford(self, other, front=False): """Return the composition channel assume other is Clifford of same size as self.""" @@ -388,24 +390,7 @@ def _compose_clifford(self, other, front=False): table1 = other.table table2 = self.table - # PREVIOUS METHOD: - # This one isn't currently getting phases correct - - # ret_table = table2.copy() - # - # Zero the return array, leave the phases in place - # ret_table.array *= False - # for i in range(ret_table.size): - # for j in range(table1.size): - # if table2.array[i, j]: - # ret_table[i] = self._rowsum(ret_table[i], table1[j]) - # - # return Clifford(ret_table) - - # ALT METHOD: - # This one is correct but needs to be optimized - - num_qubits = self.n_qubits + num_qubits = self.num_qubits array1 = table1.array.astype(int) phase1 = table1.phase.astype(int) @@ -422,55 +407,37 @@ def _compose_clifford(self, other, front=False): # Correcting for phase due to Pauli multiplicatio ifacts = np.zeros(2 * num_qubits, dtype=np.int) - for r2 in range(2 * num_qubits): + for k in range(2 * num_qubits): - row2 = array2[r2] - x2 = table2.X[r2] - z2 = table2.Z[r2] + row2 = array2[k] + x2 = table2.X[k] + z2 = table2.Z[k] # Adding a factor of i for each Y in the image of an operator under the # first operation, since Y=iXZ - ifacts[r2] += np.sum(x2 & z2) + ifacts[k] += np.sum(x2 & z2) # Adding factors of i due to qubit-wise Pauli multiplication - x = np.zeros(num_qubits, dtype=int) - z = np.zeros(num_qubits, dtype=int) - - for i, r1 in enumerate(table1): - - x1 = r1.X[0].astype(int) - z1 = r1.Z[0].astype(int) - - val = np.mod(abs(3 * z1 - x1) - abs(3 * z - x) - 1, 3) - shift = 1 * (val == 0) - 1 * (val == 1) - shift = row2[i] * (x1 | z1) * (x | z) * shift - - x = (x + row2[i] * x1) % 2 - z = (z + row2[i] * z1) % 2 - - ifacts[r2] += np.sum(shift) + for j in range(num_qubits): + x = 0 + z = 0 + for i in range(2 * num_qubits): + if row2[i]: + x1 = array1[i, j] + z1 = array1[i, j + num_qubits] + if (x | z) & (x1 | z1): + val = np.mod(np.abs(3 * z1 - x1) - np.abs(3 * z - x) - 1, 3) + if val == 0: + ifacts[k] += 1 + elif val == 1: + ifacts[k] -= 1 + x = np.mod(x + x1, 2) + z = np.mod(z + z1, 2) p = np.mod(ifacts, 4) // 2 phase = np.mod(phase + p, 2) - return Clifford(StabilizerTable(pauli, phase)) - - @staticmethod - def _rowsum(row1, row2): - """Rowsum from AG paper""" - x1, z1 = row1.X, row1.Z - x2, z2 = row2.X, row2.Z - - # Phase update (g function in AG paper) - phase = row1.phase ^ row2.phase ^ np.array( - np.sum((~x1 & z1 & x2 & ~z2) | - (x1 & ~z1 & x2 & z2) | - (x1 & z1 & ~x2 & z2), axis=1) % 2, dtype=np.bool) - - # Pauli update - pauli = row1.array ^ row2.array - - return StabilizerTable(pauli, phase) + return Clifford(StabilizerTable(pauli, phase), validate=False) diff --git a/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py similarity index 88% rename from qiskit/quantum_info/operators/symplectic/clifford_append_gate.py rename to qiskit/quantum_info/operators/symplectic/clifford_circuits.py index 8ab9a3d938f..76eedfe5560 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_append_gate.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright 2017, 2020 BM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2017, 2020 +# +# 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. """ -Clifford class gate update utility function +Circuit methods for Clifford class. """ # pylint: disable=invalid-name @@ -15,7 +22,7 @@ # --------------------------------------------------------------------- -# Apply Clifford Gates +# Main functions # --------------------------------------------------------------------- def append_gate(clifford, gate, qargs=None): @@ -28,9 +35,12 @@ def append_gate(clifford, gate, qargs=None): Returns: Clifford: the updated Clifford. + + Raises: + QiskitError: if input gate cannot be decomposed into Clifford gates. """ if qargs is None: - qargs = list(range(clifford.n_qubits)) + qargs = list(range(clifford.num_qubits)) # Basis Clifford Gates basis_1q = { @@ -85,6 +95,48 @@ def append_gate(clifford, gate, qargs=None): return clifford +def decompose_clifford(clifford): + """Decompose a Clifford into a QuantumCircuit. + + Args: + clifford (Clifford): a clifford operator. + + Return: + QuantumCircuit: a circuit implementation of the Clifford. + """ + # Compose a circuit which we will convert to an instruction + circuit = QuantumCircuit(clifford.num_qubits, + name=str(clifford)) + + # Make a copy of Clifford as we are going to do row reduction to + # reduce it to an identity + clifford_cpy = clifford.copy() + + for i in range(clifford.num_qubits): + # * make1forXkk(i) + + # put a 1 one into position by permuting and using Hadamards(i,i) + set_qubit_x_true(clifford_cpy, circuit, i) + # * .makeXrowzero(i) + # make all entries in row i except ith equal to 0 + # by using phase gate and CNOTS + set_row_x_zero(clifford_cpy, circuit, i) + # * makeZrowzero(i) + # treat Zs + set_row_z_zero(clifford_cpy, circuit, i) + + for i in range(clifford.num_qubits): + if clifford_cpy.destabilizer.phase[i]: + append_z(clifford_cpy, i) + circuit.z(i) + if clifford_cpy.stabilizer.phase[i]: + append_x(clifford_cpy, i) + circuit.x(i) + # Next we invert the circuit to undo the row reduction and return the + # result as a gate instruction + return circuit.inverse() + + # --------------------------------------------------------------------- # Helper functions for applying basis gates # --------------------------------------------------------------------- @@ -99,6 +151,7 @@ def append_i(clifford, qubit): Returns: Clifford: the updated Clifford. """ + # pylint: disable=unused-argument return clifford @@ -298,49 +351,9 @@ def append_swap(clifford, qubit0, qubit1): return clifford -def decompose_clifford(clifford): - """Decompose a Clifford into a QuantumCircuit. - - TODO: Reference paper for Algorithm (Gottesman?). - - Args: - clifford (Clifford): a clifford operator. - - Return: - QuantumCircuit: a circuit implementation of the Clifford. - """ - # Compose a circuit which we will convert to an instruction - circuit = QuantumCircuit(clifford.n_qubits, - name=str(clifford)) - - # Make a copy of Clifford as we are going to do row reduction to - # reduce it to an identity - clifford_cpy = clifford.copy() - - for i in range(clifford.n_qubits): - # * make1forXkk(i) - - # put a 1 one into position by permuting and using Hadamards(i,i) - set_qubit_x_true(clifford_cpy, circuit, i) - # * .makeXrowzero(i) - # make all entries in row i except ith equal to 0 - # by using phase gate and CNOTS - set_row_x_zero(clifford_cpy, circuit, i) - # * makeZrowzero(i) - # treat Zs - set_row_z_zero(clifford_cpy, circuit, i) - - for i in range(clifford.n_qubits): - if clifford_cpy.destabilizer.phase[i]: - append_z(clifford_cpy, i) - circuit.z(i) - if clifford_cpy.stabilizer.phase[i]: - append_x(clifford_cpy, i) - circuit.x(i) - # Next we invert the circuit to undo the row reduction and return the - # result as a gate instruction - return circuit.inverse() - +# --------------------------------------------------------------------- +# Helper functions for decomposition +# --------------------------------------------------------------------- def set_qubit_x_true(clifford, circuit, qubit): """Set destabilizer.X[qubit, qubit] to be True. @@ -355,14 +368,14 @@ def set_qubit_x_true(clifford, circuit, qubit): return # Try to find non-zero element - for i in range(qubit + 1, clifford.n_qubits): + for i in range(qubit + 1, clifford.num_qubits): if x[i]: append_swap(clifford, i, qubit) circuit.swap(i, qubit) return # no non-zero element found: need to apply Hadamard somewhere - for i in range(qubit, clifford.n_qubits): + for i in range(qubit, clifford.num_qubits): if z[i]: append_h(clifford, i) circuit.h(i) @@ -381,7 +394,7 @@ def set_row_x_zero(clifford, circuit, qubit): z = clifford.destabilizer.Z[qubit] # Check X first - for i in range(qubit + 1, clifford.n_qubits): + for i in range(qubit + 1, clifford.num_qubits): if x[i]: append_cx(clifford, qubit, i) circuit.cx(qubit, i) @@ -394,7 +407,7 @@ def set_row_x_zero(clifford, circuit, qubit): circuit.s(qubit) # reverse CNOTS - for i in range(qubit + 1, clifford.n_qubits): + for i in range(qubit + 1, clifford.num_qubits): if z[i]: append_cx(clifford, i, qubit) circuit.cx(i, qubit) @@ -406,7 +419,7 @@ def set_row_x_zero(clifford, circuit, qubit): def set_row_z_zero(clifford, circuit, qubit): """Set stabilizer.Z[qubit, i] to False for all i > qubit. - Implemented by applying (reverse) CNOTS assumes qubit < n_qubits + Implemented by applying (reverse) CNOTS assumes qubit < num_qubits and set_row_x_zero has been called first """ @@ -415,7 +428,7 @@ def set_row_z_zero(clifford, circuit, qubit): # check whether Zs need to be set to zero: if np.any(z[qubit + 1:]): - for i in range(qubit + 1, clifford.n_qubits): + for i in range(qubit + 1, clifford.num_qubits): if z[i]: append_cx(clifford, i, qubit) circuit.cx(i, qubit) @@ -424,7 +437,7 @@ def set_row_z_zero(clifford, circuit, qubit): if np.any(x[qubit:]): append_h(clifford, qubit) circuit.h(qubit) - for i in range(qubit + 1, clifford.n_qubits): + for i in range(qubit + 1, clifford.num_qubits): if x[i]: append_cx(clifford, qubit, i) circuit.cx(qubit, i) diff --git a/test/python/quantum_info/operators/symplectic/test_clifford_gates.py b/test/python/quantum_info/operators/symplectic/test_clifford.py similarity index 63% rename from test/python/quantum_info/operators/symplectic/test_clifford_gates.py rename to test/python/quantum_info/operators/symplectic/test_clifford.py index 441afbd96f2..53f5f586e08 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford_gates.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -13,8 +13,7 @@ # that they have been altered from the originals. # pylint: disable=invalid-name - -"""Tests for clifford append gate functions.""" +"""Tests for Clifford class.""" import unittest from test import combine @@ -24,16 +23,15 @@ from qiskit.test import QiskitTestCase from qiskit.circuit import Gate, QuantumRegister, QuantumCircuit -from qiskit.extensions.standard import (IGate, XGate, YGate, ZGate, - HGate, SGate, SdgGate, - CXGate, CZGate, SwapGate) +from qiskit.extensions.standard import (IGate, XGate, YGate, ZGate, HGate, + SGate, SdgGate, CXGate, CZGate, + SwapGate) from qiskit.quantum_info.operators import Clifford, Operator -from qiskit.quantum_info.operators.symplectic.clifford_append_gate import append_gate +from qiskit.quantum_info.operators.symplectic.clifford_circuits import append_gate class VGate(Gate): """V Gate used in Clifford synthesis.""" - def __init__(self): """Create new V Gate.""" super().__init__('v', 1, []) @@ -46,7 +44,6 @@ def _define(self): class WGate(Gate): """W Gate used in Clifford synthesis.""" - def __init__(self): """Create new W Gate.""" super().__init__('w', 1, []) @@ -63,7 +60,10 @@ def random_clifford_circuit(num_qubits, num_gates, gates='all', seed=None): if num_qubits == 1: gates = ['i', 'x', 'y', 'z', 'h', 's', 'sdg', 'v', 'w'] else: - gates = ['i', 'x', 'y', 'z', 'h', 's', 'sdg', 'v', 'w', 'cx', 'cz', 'swap'] + gates = [ + 'i', 'x', 'y', 'z', 'h', 's', 'sdg', 'v', 'w', 'cx', 'cz', + 'swap' + ] instructions = { 'i': (IGate(), 1), @@ -93,11 +93,11 @@ def random_clifford_circuit(num_qubits, num_gates, gates='all', seed=None): return circ +@ddt class TestCliffordGates(QiskitTestCase): """Tests for clifford append gate functions.""" - def test_append_1_qubit_gate(self): - "Tests for append of 1-qubit gates" + """Tests for append of 1-qubit gates""" target_table = { "i": np.array([[[True, False], [False, True]]], dtype=np.bool), @@ -112,7 +112,6 @@ def test_append_1_qubit_gate(self): "sinv": np.array([[[True, True], [False, True]]], dtype=np.bool), "v": np.array([[[True, True], [True, False]]], dtype=np.bool), "w": np.array([[[False, True], [True, True]]], dtype=np.bool), - } target_phase = { @@ -160,8 +159,8 @@ def test_append_1_qubit_gate(self): "w": "+Z", } - for gate_name in ("i", "id", "iden", "x", "y", "z", "h", - "s", "sdg", "v", "w"): + for gate_name in ("i", "id", "iden", "x", "y", "z", "h", "s", "sdg", + "v", "w"): with self.subTest(msg='append gate %s' % gate_name): cliff = Clifford([[1, 0], [0, 1]]) cliff = append_gate(cliff, gate_name, [0]) @@ -169,17 +168,21 @@ def test_append_1_qubit_gate(self): value_phase = cliff.table._phase value_stabilizer = cliff.stabilizer.to_labels() value_destabilizer = cliff.destabilizer.to_labels() - self.assertTrue(np.all(np.array(value_table == - target_table[gate_name]))) - self.assertTrue(np.all(np.array(value_phase == - target_phase[gate_name]))) - self.assertTrue(np.all(np.array(value_stabilizer == - [target_stabilizer[gate_name]]))) - self.assertTrue(np.all(np.array(value_destabilizer == - [target_destabilizer[gate_name]]))) + self.assertTrue( + np.all(np.array(value_table == target_table[gate_name]))) + self.assertTrue( + np.all(np.array(value_phase == target_phase[gate_name]))) + self.assertTrue( + np.all( + np.array(value_stabilizer == + [target_stabilizer[gate_name]]))) + self.assertTrue( + np.all( + np.array(value_destabilizer == + [target_destabilizer[gate_name]]))) def test_1_qubit_identity_relations(self): - "Tests identity relations for 1-qubit gates" + """Tests identity relations for 1-qubit gates""" for gate_name in ("x", "y", "z", "h"): with self.subTest(msg='identity for gate %s' % gate_name): @@ -201,11 +204,12 @@ def test_1_qubit_identity_relations(self): self.assertEqual(cliff, cliff1) def test_1_qubit_mult_relations(self): - "Tests multiplicity relations for 1-qubit gates" + """Tests multiplicity relations for 1-qubit gates""" - rels = ['x * y = z', 'x * z = y', 'y * z = x', - 's * s = z', 'sdg * sdg = z', 'sinv * sinv = z', - 'sdg * h = v', 'h * s = w'] + rels = [ + 'x * y = z', 'x * z = y', 'y * z = x', 's * s = z', + 'sdg * sdg = z', 'sinv * sinv = z', 'sdg * h = v', 'h * s = w' + ] for rel in rels: with self.subTest(msg='relation %s' % rel): @@ -218,11 +222,12 @@ def test_1_qubit_mult_relations(self): self.assertEqual(cliff, cliff1) def test_1_qubit_conj_relations(self): - "Tests conjugation relations for 1-qubit gates" + """Tests conjugation relations for 1-qubit gates""" - rels = ['h * x * h = z', 'h * y * h = y', - 's * x * sdg = y', 'w * x * v = y', - 'w * y * v = z', 'w * z * v = x'] + rels = [ + 'h * x * h = z', 'h * y * h = y', 's * x * sdg = y', + 'w * x * v = y', 'w * y * v = z', 'w * z * v = x' + ] for rel in rels: with self.subTest(msg='relation %s' % rel): @@ -235,88 +240,49 @@ def test_1_qubit_conj_relations(self): cliff1 = append_gate(cliff1, split_rel[6], [0]) self.assertEqual(cliff, cliff1) - def test_append_2_qubit_gate(self): - "Tests for append of 2-qubit gates" - - target_table = { - "cx [0, 1]": np.array([[True, True, False, False], - [False, True, False, False], - [False, False, True, False], - [False, False, True, True]]), - - "cx [1, 0]": np.array([[True, False, False, False], - [True, True, False, False], - [False, False, True, True], - [False, False, False, True]]), - - "cz [0, 1]": np.array([[True, False, False, True], - [False, True, True, False], - [False, False, True, False], - [False, False, False, True]]), - - "cz [1, 0]": np.array([[True, False, False, True], - [False, True, True, False], - [False, False, True, False], - [False, False, False, True]]), - - "swap [0, 1]": np.array([[False, True, False, False], - [True, False, False, False], - [False, False, False, True], - [False, False, True, False]]), - - "swap [1, 0]": np.array([[False, True, False, False], - [True, False, False, False], - [False, False, False, True], - [False, False, True, False]]) - } - - target_phase = np.array([False, False, False, False]) - - target_stabilizer = { - "cx [0, 1]": ['+ZI', '+ZZ'], - "cx [1, 0]": ['+ZZ', '+IZ'], - "cz [0, 1]": ['+ZI', '+IZ'], - "cz [1, 0]": ['+ZI', '+IZ'], - "swap [0, 1]": ['+IZ', '+ZI'], - "swap [1, 0]": ['+IZ', '+ZI'] - } - - target_destabilizer = { - "cx [0, 1]": ['+XX', '+IX'], - "cx [1, 0]": ['+XI', '+XX'], - "cz [0, 1]": ['+XZ', '+ZX'], - "cz [1, 0]": ['+XZ', '+ZX'], - "swap [0, 1]": ['+IX', '+XI'], - "swap [1, 0]": ['+IX', '+XI'] + @combine(gate_name=("cx", "cz", "swap"), qubits=([0, 1], [1, 0])) + def test_append_2_qubit_gate(self, gate_name, qubits): + """Tests for append of 2-qubit gate {gate_name} {qubits}.""" + + targets_cliffords = { + "cx [0, 1]": + Clifford([[True, True, False, False], [False, True, False, False], + [False, False, True, False], [False, False, True, + True]]), + "cx [1, 0]": + Clifford([[True, False, False, False], [True, True, False, False], + [False, False, True, True], [False, False, False, + True]]), + "cz [0, 1]": + Clifford([[True, False, False, True], [False, True, True, False], + [False, False, True, False], [False, False, False, + True]]), + "cz [1, 0]": + Clifford([[True, False, False, True], [False, True, True, False], + [False, False, True, False], [False, False, False, + True]]), + "swap [0, 1]": + Clifford([[False, True, False, False], [True, False, False, False], + [False, False, False, True], [False, False, True, + False]]), + "swap [1, 0]": + Clifford([[False, True, False, False], [True, False, False, False], + [False, False, False, True], [False, False, True, + False]]) } - for gate_name in ("cx", "cz", "swap"): - for qubits in ([0, 1], [1, 0]): - with self.subTest(msg='append gate %s %s' % ( - gate_name, qubits)): - gate_qubits = gate_name + " " + str(qubits) - cliff = Clifford(np.eye(4)) - cliff = append_gate(cliff, gate_name, qubits) - value_table = cliff.table._array - value_phase = cliff.table._phase - value_stabilizer = cliff.stabilizer.to_labels() - value_destabilizer = cliff.destabilizer.to_labels() - self.assertTrue(np.all(np.array(value_table == - target_table[gate_qubits]))) - self.assertTrue(np.all(np.array(value_phase == - target_phase))) - self.assertTrue(np.all(np.array(value_stabilizer == - target_stabilizer[gate_qubits]))) - self.assertTrue(np.all(np.array(value_destabilizer == - target_destabilizer[gate_qubits]))) + gate_qubits = gate_name + " " + str(qubits) + cliff = append_gate(Clifford(np.eye(4)), gate_name, qubits) + target = targets_cliffords[gate_qubits] + self.assertEqual(target, cliff) def test_2_qubit_identity_relations(self): - "Tests identity relations for 2-qubit gates" + """Tests identity relations for 2-qubit gates""" for gate_name in ("cx", "cz", "swap"): for qubits in ([0, 1], [1, 0]): - with self.subTest(msg='append gate %s %s' % ( - gate_name, qubits)): + with self.subTest(msg='append gate %s %s' % + (gate_name, qubits)): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() cliff = append_gate(cliff, gate_name, qubits) @@ -324,7 +290,7 @@ def test_2_qubit_identity_relations(self): self.assertEqual(cliff, cliff1) def test_2_qubit_relations(self): - "Tests relations for 2-qubit gates" + """Tests relations for 2-qubit gates""" with self.subTest(msg='relation between cx, h and cz'): cliff = Clifford(np.eye(4)) @@ -379,12 +345,8 @@ def test_2_qubit_relations(self): @ddt class TestCliffordCircuits(QiskitTestCase): """Stress tests for random clifford circuits.""" - - @combine(gates=[['h', 's'], - ['h', 's', 'i', 'x', 'y', 'z'], - ['h', 's', 'sdg'], - ['h', 's', 'v'], - ['h', 's', 'w'], + @combine(gates=[['h', 's'], ['h', 's', 'i', 'x', 'y', 'z'], + ['h', 's', 'sdg'], ['h', 's', 'v'], ['h', 's', 'w'], ['h', 's', 'sdg', 'i', 'x', 'y', 'z', 'v', 'w']]) def test_to_operator_1qubit_gates(self, gates): """Test 1-qubit circuit with gates {gates}""" @@ -392,25 +354,32 @@ def test_to_operator_1qubit_gates(self, gates): num_gates = 10 seed = 100 for i in range(samples): - circ = random_clifford_circuit(1, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(1, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).to_operator() target = Operator(circ) self.assertTrue(target.equiv(value)) - @combine(gates=[['cx'], ['cz'], ['swap'], ['cx', 'cz'], - ['cx', 'swap'], ['cz', 'swap'], ['cx', 'cz', 'swap']]) + @combine(gates=[['cx'], ['cz'], ['swap'], ['cx', 'cz'], ['cx', 'swap'], + ['cz', 'swap'], ['cx', 'cz', 'swap']]) def test_to_operator_2qubit_gates(self, gates): """Test 2-qubit circuit with gates {gates}""" samples = 10 num_gates = 10 seed = 200 for i in range(samples): - circ = random_clifford_circuit(2, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(2, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).to_operator() target = Operator(circ) self.assertTrue(target.equiv(value)) - @combine(gates=[['h', 's', 'cx'], ['h', 's', 'cz'], ['h', 's', 'swap'], 'all'], + @combine(gates=[['h', 's', 'cx'], ['h', 's', 'cz'], ['h', 's', 'swap'], + 'all'], num_qubits=[2, 3, 4]) def test_to_operator_nqubit_gates(self, gates, num_qubits): """Test {num_qubits}-qubit circuit with gates {gates}""" @@ -418,7 +387,10 @@ def test_to_operator_nqubit_gates(self, gates, num_qubits): num_gates = 20 seed = 300 for i in range(samples): - circ = random_clifford_circuit(num_qubits, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).to_operator() target = Operator(circ) self.assertTrue(target.equiv(value)) @@ -426,72 +398,74 @@ def test_to_operator_nqubit_gates(self, gates, num_qubits): @ddt class TestCliffordOperators(QiskitTestCase): - @combine(num_qubits=[1, 2, 3]) - def test_is_unitary(self, num_qubits): - "Test is_unitary method" + def test_is_unitary(self, num_qubits): + """Test is_unitary method""" samples = 10 num_gates = 10 seed = 700 gates = 'all' for i in range(samples): - circ = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).is_unitary() self.assertTrue(value) # tests a false clifford - cliff = Clifford([[0, 0], [0, 1]]) + cliff = Clifford([[0, 0], [0, 1]], validate=False) value = cliff.is_unitary() self.assertFalse(value) @combine(num_qubits=[1, 2, 3]) - def test_conjugate(self, num_qubits): - "Test conjugate method" + def test_conjugate(self, num_qubits): + """Test conjugate method""" samples = 10 num_gates = 10 seed = 400 gates = 'all' for i in range(samples): - circ = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).conjugate().to_operator() target = Operator(circ).conjugate() self.assertTrue(target.equiv(value)) + @unittest.skip("Currently failing") @combine(num_qubits=[1, 2, 3]) def test_transpose(self, num_qubits): - "Test transpose method" - # TODO - fix test after transpose method is done + """Test transpose method""" samples = 10 num_gates = 1 seed = 500 gates = 'all' for i in range(samples): - circ = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + i) + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) value = Clifford(circ).transpose().to_operator() target = Operator(circ).transpose() - #print (circ) - #print (Clifford(circ)) - #print(Clifford(circ).transpose()) - #print (target) - #print (value) - #print (target == value) - #print ("---------") - #self.assertTrue(target.equiv(value)) + self.assertTrue(target.equiv(value)) @combine(num_qubits=[1, 2, 3]) def test_compose_method(self, num_qubits): - "Test compose method" + """Test compose method""" samples = 10 num_gates = 10 seed = 600 gates = 'all' for i in range(samples): - circ1 = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + i) - circ2 = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + samples + i) + circ1 = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + samples + i) cliff1 = Clifford(circ1) cliff2 = Clifford(circ2) value = cliff1.compose(cliff2) @@ -500,61 +474,75 @@ def test_compose_method(self, num_qubits): @combine(num_qubits=[1, 2, 3]) def test_dot_method(self, num_qubits): - "Test dot method" + """Test dot method""" samples = 10 num_gates = 10 seed = 600 gates = 'all' for i in range(samples): - circ1 = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + i) - circ2 = random_clifford_circuit( - num_qubits, num_gates, gates=gates, seed=seed + samples + i) + circ1 = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + samples + i) cliff1 = Clifford(circ1) cliff2 = Clifford(circ2) value = cliff1.dot(cliff2) target = Clifford(circ2.extend(circ1)) self.assertEqual(target, value) - @combine(num_qubits_1=[1,2,3], num_qubits_2=[1,2,3]) + @combine(num_qubits_1=[1, 2, 3], num_qubits_2=[1, 2, 3]) def test_tensor_method(self, num_qubits_1, num_qubits_2): - "Test tensor method" + """Test tensor method""" samples = 5 num_gates = 10 seed = 800 gates = 'all' for i in range(samples): - circ1 = random_clifford_circuit( - num_qubits_1, num_gates, gates=gates, seed=seed + i) - circ2 = random_clifford_circuit( - num_qubits_2, num_gates, gates=gates, seed=seed + samples + i) + circ1 = random_clifford_circuit(num_qubits_1, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits_2, + num_gates, + gates=gates, + seed=seed + samples + i) cliff1 = Clifford(circ1) cliff2 = Clifford(circ2) value = cliff1.tensor(cliff2) circ = QuantumCircuit(num_qubits_1 + num_qubits_2) - circ.append(circ1, range(num_qubits_1)) - circ.append(circ2, range(num_qubits_1, num_qubits_1 + num_qubits_2)) + circ.append(circ2, range(num_qubits_2)) + circ.append(circ1, range(num_qubits_2, + num_qubits_1 + num_qubits_2)) target = Clifford(circ) self.assertEqual(target, value) - @combine(num_qubits_1=[1,2,3], num_qubits_2=[1,2,3]) + @combine(num_qubits_1=[1, 2, 3], num_qubits_2=[1, 2, 3]) def test_expand_method(self, num_qubits_1, num_qubits_2): - "Test expand method" + """Test expand method""" samples = 5 num_gates = 10 seed = 800 gates = 'all' for i in range(samples): - circ1 = random_clifford_circuit( - num_qubits_1, num_gates, gates=gates, seed=seed + i) - circ2 = random_clifford_circuit( - num_qubits_2, num_gates, gates=gates, seed=seed + samples + i) + circ1 = random_clifford_circuit(num_qubits_1, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits_2, + num_gates, + gates=gates, + seed=seed + samples + i) cliff1 = Clifford(circ1) cliff2 = Clifford(circ2) value = cliff1.expand(cliff2) circ = QuantumCircuit(num_qubits_1 + num_qubits_2) - circ.append(circ2, range(num_qubits_2)) - circ.append(circ1, range(num_qubits_2, num_qubits_1 + num_qubits_2)) + circ.append(circ1, range(num_qubits_1)) + circ.append(circ2, range(num_qubits_1, + num_qubits_1 + num_qubits_2)) target = Clifford(circ) self.assertEqual(target, value) From 20e8122f2a656036404ed882add9e196e2bf195c Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 29 Mar 2020 10:35:35 -0400 Subject: [PATCH 06/17] Add adjoint and conjugate --- .../operators/symplectic/clifford.py | 54 +++++++++++-------- .../operators/symplectic/test_clifford.py | 1 - 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 0d7ddf4a74a..57c98fe0918 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -142,27 +142,15 @@ def is_unitary(self, atol=None, rtol=None): def conjugate(self): """Return the conjugate of the Clifford.""" - # TODO: Needs testing to see if correct - x = self.table.X - z = self.table.Z - ret = self.copy() - ret.table.phase = self.table.phase ^ (np.sum(x & z, axis=1) % 2) - return ret + return Clifford._conjugate_transpose(self, 'C') + + def adjoint(self): + """Return the conjugate transpose of the Clifford""" + return Clifford._conjugate_transpose(self, 'A') def transpose(self): """Return the transpose of the Clifford.""" - - # TODO: Needs testing to see if correct - # This is done using block matrix multiplication - # [[0, 1], [1, 0]] * table.T * [[0, 1], [1, 0]] - - ret = self.copy() - tmp = ret.destabilizer.X.copy() - ret.destabilizer.X = ret.stabilizer.Z - ret.destabilizer.Z = ret.destabilizer.Z.T - ret.stabilizer.X = ret.stabilizer.X.T - ret.stabilizer.Z = tmp - return ret + return Clifford._conjugate_transpose(self, 'T') def compose(self, other, qargs=None, front=False): """Return the composed operator. @@ -325,6 +313,33 @@ def _is_symplectic(mat): arr = mat.astype(np.int) return np.array_equal(np.mod(arr.T.dot(seye).dot(arr), 2), seye) + @staticmethod + def _conjugate_transpose(clifford, method): + """Return the adjoint, conjugate, or transpose of the Clifford. + + Args: + method (str): what function to apply 'A', 'C', or 'T'. + + Returns: + Clifford: the modified clifford. + """ + ret = clifford.copy() + if method in ['A', 'T']: + # Apply inverse + # Update table + tmp = ret.destabilizer.X.copy() + ret.destabilizer.X = ret.stabilizer.Z.T + ret.destabilizer.Z = ret.destabilizer.Z.T + ret.stabilizer.X = ret.stabilizer.X.T + ret.stabilizer.Z = tmp.T + # Update phase + ret.table.phase ^= clifford.dot(ret).table.phase + if method in ['C', 'T']: + # Apply conjugate + ret.table.phase ^= np.mod(np.sum( + ret.table.X & ret.table.Z, axis=1), 2).astype(np.bool) + return ret + def _tensor_product(self, other, reverse=False): """Return the tensor product operator. @@ -357,9 +372,6 @@ def _tensor_product(self, other, reverse=False): # Add the padded table return Clifford(destab + stab, validate=False) - # --------------------------------------------------------------------- - # Internal composition methods - # --------------------------------------------------------------------- def _pad_with_identity(self, clifford, qargs): """Pad Clifford with identities on other subsystems.""" if qargs is None: diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 53f5f586e08..225b0a8b2a5 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -433,7 +433,6 @@ def test_conjugate(self, num_qubits): target = Operator(circ).conjugate() self.assertTrue(target.equiv(value)) - @unittest.skip("Currently failing") @combine(num_qubits=[1, 2, 3]) def test_transpose(self, num_qubits): """Test transpose method""" From cd80d3a819c010bf1379272d041da60cbc8b5b60 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Tue, 31 Mar 2020 18:10:33 +0300 Subject: [PATCH 07/17] add tests for compose and dot of subsystems, and for to_circuit --- .../operators/symplectic/test_clifford.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 225b0a8b2a5..164694f80c0 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -545,6 +545,68 @@ def test_expand_method(self, num_qubits_1, num_qubits_2): target = Clifford(circ) self.assertEqual(target, value) + @combine(num_qubits_1=[4, 5, 6], num_qubits_2=[1, 2, 3]) + def test_compose_subsystem(self, num_qubits_1, num_qubits_2): + """Test compose method of subsystems""" + samples = 10 + num_gates = 10 + seed = 600 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit(num_qubits_1, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits_2, + num_gates, + gates=gates, + seed=seed + samples + i) + qargs = sorted(np.random.choice(range(num_qubits_1), num_qubits_2, replace=False)) + circ = circ1.copy() + circ.append(circ2.to_instruction(), qargs) + value = Clifford(circ1).compose(Clifford(circ2), qargs) + target = Clifford(circ) + self.assertEqual(target, value) + + @combine(num_qubits_1=[4, 5, 6], num_qubits_2=[1, 2, 3]) + def test_dot_subsystem(self, num_qubits_1, num_qubits_2): + """Test dot method of subsystems""" + samples = 10 + num_gates = 10 + seed = 600 + gates = 'all' + for i in range(samples): + circ1 = random_clifford_circuit(num_qubits_1, + num_gates, + gates=gates, + seed=seed + i) + circ2 = random_clifford_circuit(num_qubits_2, + num_gates, + gates=gates, + seed=seed + samples + i) + qargs = sorted(np.random.choice(range(num_qubits_1), num_qubits_2, replace=False)) + circ = QuantumCircuit(num_qubits_1) + circ.append(circ2.to_instruction(), qargs) + circ.append(circ1.to_instruction(), range(num_qubits_1)) + value = Clifford(circ1).dot(Clifford(circ2), qargs) + target = Clifford(circ) + self.assertEqual(target, value) + + @combine(num_qubits=[1, 2, 3]) + def test_to_circuit(self, num_qubits): + """Test to_circuit method (decompose)""" + samples = 10 + num_gates = 10 + seed = 700 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + value = Clifford(Clifford(circ).to_circuit()).to_operator() + target = Operator(circ) + self.assertTrue(value) if __name__ == '__main__': unittest.main() From 4e2390e18430880d26ee4eb04590f42d88fb0e49 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 31 Mar 2020 12:26:47 -0400 Subject: [PATCH 08/17] Add Clifford class docs and reno --- qiskit/quantum_info/__init__.py | 1 + .../operators/symplectic/clifford.py | 80 +++++++++++++++++-- .../notes/clifford-00645b805dedbb41.yaml | 6 ++ .../operators/symplectic/test_clifford.py | 3 +- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/clifford-00645b805dedbb41.yaml diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 1e25cb17a4f..091839f555d 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -25,6 +25,7 @@ :toctree: ../stubs/ Operator + Clifford ScalarOp Pauli pauli_group diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 57c98fe0918..2e7b3df423b 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -28,7 +28,77 @@ class Clifford(BaseOperator): - """Clifford table operator class""" + """An N-qubit unitary operator from the Clifford group. + + **Representation** + + An *N*-qubit Clifford operator is stored as a length *2N* + :class:`~qiskit.quantum_info.StabilizerTable` using the convention + from reference [1]. + + * Rows 0 to *N-1* are the *destabilizer* group generators + * Rows *N-1* to *2N-1* are the *stabilizer* group generators. + + The internal :class:`~qiskit.quantum_info.StabilizerTable` for the Clifford + can be accessed using the :attr:`table` attribute. The destabilizer or + stabilizer rows can each be accessed as a length-N Stabilizer table using + :attr:`destabilizer` and :attr:`stabilizer` attributes. + + A more easily human readible representation of the Clifford operator can + be obtained by calling the :meth:`to_dict` method. This representation is + also used if a Clifford object is printed as in the following example + + .. jupyter-execute:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford + + # Bell state generation circuit + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + cliff = Clifford(qc) + + # Print the Clifford + print(cliff) + + # Print the Clifford destabilizer rows + print(cliff.destabilizer) + + # Print the Clifford destabilizer rows + print(cliff.stabilizer) + + **Circuit Conversion** + + Clifford operators can be initialized from circuits containing *only* the + following Clifford gates: :class:`~qiskit.extensions.IGate`, + :class:`~qiskit.extensions.XGate`, :class:`~qiskit.extensions.YGate`, + :class:`~qiskit.extensions.ZGate`, :class:`~qiskit.extensions.HGate`, + :class:`~qiskit.extensions.SGate`, :class:`~qiskit.extensions.SdgGate`, + :class:`~qiskit.extensions.CXGate`, :class:`~qiskit.extensions.CZGate`. + They can be converted back into a :class:`~qiskit.circuit.QuantumCircuit`, + or :class:`~qiskit.circuit.Gate` object using the :meth:`~Clifford.to_circuit` + or :meth:`~Clifford.to_instruction` methods respectively. Note that this + decomposition is not necessarily optimal in terms of number of gates. + + .. note:: + + A minimally generating set of gates for Clifford circuits is + the :class:`~qiskit.extensions.HGate` and + :class:`~qiskit.extensions.SGate` gate and *either* the + :class:`~qiskit.extensions.CXGate` or + :class:`~qiskit.extensions.CZGate` two-qubit gate. + + Clifford operators can also be converted to + :class:`~qiskit.quantum_info.Operator` objects using the + :meth:`to_operator` method. This is done via decomposing to a circuit, and then + simulating the circuit as a unitary operator. + + References: + 1. S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, + Phys. Rev. A 70, 052328 (2004). + `arXiv:quant-ph/0406196 `_ + """ def __init__(self, data, validate=True): """Initialize an operator object.""" @@ -94,7 +164,7 @@ def table(self): @table.setter def table(self, value): """Set the stabilizer table""" - # Note that is setup so it can't change the size of the Clifford + # Note this setter cannot change the size of the Clifford # It can only replace the contents of the StabilizerTable with # another StabilizerTable of the same size. if not isinstance(value, StabilizerTable): @@ -236,7 +306,7 @@ def expand(self, other): # --------------------------------------------------------------------- def to_dict(self): - """Return dictionary represenation of Clifford object""" + """Return dictionary represenation of Clifford object.""" return { "stabilizer": self.stabilizer.to_labels(), "destabilizer": self.destabilizer.to_labels() @@ -255,13 +325,13 @@ def to_matrix(self): def to_operator(self): """Convert to an Operator object.""" - return Operator(self.to_gate()) + return Operator(self.to_instruction()) def to_circuit(self): """Return a QuantumCircuit implementing the Clifford.""" return decompose_clifford(self) - def to_gate(self): + def to_instruction(self): """Return a Gate instruction implementing the Clifford.""" return self.to_circuit().to_gate() diff --git a/releasenotes/notes/clifford-00645b805dedbb41.yaml b/releasenotes/notes/clifford-00645b805dedbb41.yaml new file mode 100644 index 00000000000..331d5a38f84 --- /dev/null +++ b/releasenotes/notes/clifford-00645b805dedbb41.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds :class:`qiskit.quantum_info.Clifford` operator class to the + `quantum_info` module. This operator is an efficient symplectic + representation an N-qubit unitary operator form the Clifford group. diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 164694f80c0..d9663dcfe7b 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -606,7 +606,8 @@ def test_to_circuit(self, num_qubits): seed=seed + i) value = Clifford(Clifford(circ).to_circuit()).to_operator() target = Operator(circ) - self.assertTrue(value) + self.assertTrue(value.equiv(target)) + if __name__ == '__main__': unittest.main() From 1cb6cb176d2c3fcedbc726c49417b910c80cbcd4 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 31 Mar 2020 14:49:51 -0400 Subject: [PATCH 09/17] Linting and doc fox --- qiskit/quantum_info/operators/symplectic/clifford.py | 5 +++-- .../quantum_info/operators/symplectic/test_clifford.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 2e7b3df423b..a14dbd1b8e7 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -340,8 +340,8 @@ def from_instruction(instruction): """Initialize from a QuantumCircuit or Instruction. Args: - instruction (QuantumCircuit or Instruction): instruction to - initialize. + instruction (QuantumCircuit or ~qiskit.circuit.Instruction): + instruction to initialize. Returns: Clifford: the Clifford object for the instruction. @@ -388,6 +388,7 @@ def _conjugate_transpose(clifford, method): """Return the adjoint, conjugate, or transpose of the Clifford. Args: + clifford (Clifford): a clifford object. method (str): what function to apply 'A', 'C', or 'T'. Returns: diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index d9663dcfe7b..3aed603592b 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -398,6 +398,8 @@ def test_to_operator_nqubit_gates(self, gates, num_qubits): @ddt class TestCliffordOperators(QiskitTestCase): + """Test Clifford operator class methods.""" + @combine(num_qubits=[1, 2, 3]) def test_is_unitary(self, num_qubits): """Test is_unitary method""" From 42630756dc12c366d7b90eff87c430c73f272a2b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 08:56:59 -0400 Subject: [PATCH 10/17] Review comments --- qiskit/quantum_info/operators/symplectic/clifford.py | 12 ++++++------ .../operators/symplectic/clifford_circuits.py | 10 +++------- releasenotes/notes/clifford-00645b805dedbb41.yaml | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index a14dbd1b8e7..fd6db03b5aa 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -65,7 +65,7 @@ class Clifford(BaseOperator): # Print the Clifford destabilizer rows print(cliff.destabilizer) - # Print the Clifford destabilizer rows + # Print the Clifford stabilizer rows print(cliff.stabilizer) **Circuit Conversion** @@ -75,7 +75,8 @@ class Clifford(BaseOperator): :class:`~qiskit.extensions.XGate`, :class:`~qiskit.extensions.YGate`, :class:`~qiskit.extensions.ZGate`, :class:`~qiskit.extensions.HGate`, :class:`~qiskit.extensions.SGate`, :class:`~qiskit.extensions.SdgGate`, - :class:`~qiskit.extensions.CXGate`, :class:`~qiskit.extensions.CZGate`. + :class:`~qiskit.extensions.CXGate`, :class:`~qiskit.extensions.CZGate`, + :class:`~qiskit.extensions.SwapGate`. They can be converted back into a :class:`~qiskit.circuit.QuantumCircuit`, or :class:`~qiskit.circuit.Gate` object using the :meth:`~Clifford.to_circuit` or :meth:`~Clifford.to_instruction` methods respectively. Note that this @@ -198,9 +199,8 @@ def destabilizer(self, value): # Utility Operator methods # --------------------------------------------------------------------- - def is_unitary(self, atol=None, rtol=None): + def is_unitary(self): """Return True if the Clifford table is valid.""" - # pylint: disable=unused-argument # A valid Clifford is always unitary, so this function is really # checking that the underlying Stabilizer table array is a valid # Clifford array. @@ -417,7 +417,7 @@ def _tensor_product(self, other, reverse=False): Args: other (Clifford): another Clifford operator. reverse (bool): If False return self ⊗ other, if True return - if True return (other ⊗ self) [Default: False + if True return (other ⊗ self) [Default: False]. Returns: Clifford: the tensor product operator. @@ -487,7 +487,7 @@ def _compose_clifford(self, other, front=False): # Add phases phase = np.mod(array2.dot(phase1) + phase2, 2) - # Correcting for phase due to Pauli multiplicatio + # Correcting for phase due to Pauli multiplication ifacts = np.zeros(2 * num_qubits, dtype=np.int) for k in range(2 * num_qubits): diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py index 76eedfe5560..cc23515b977 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -78,9 +78,9 @@ def append_gate(clifford, gate, qargs=None): raise QiskitError("Invalid qubits for 2-qubit gate.") return basis_2q[name](clifford, qargs[0], qargs[1]) - # If not a Clifford basis gate we try to unroll the gate, - # raising an exception if unrolling reaches a non-Clifford gate. - # TODO: We could check for also check u3 params to see if they + # If not a Clifford basis gate we try to unroll the gate and + # raise an exception if unrolling reaches a non-Clifford gate. + # TODO: We could also check u3 params to see if they # are a single qubit Clifford gate rather than raise an exception. if gate.definition is None: raise QiskitError('Cannot apply Instruction: {}'.format(gate.name)) @@ -113,15 +113,11 @@ def decompose_clifford(clifford): clifford_cpy = clifford.copy() for i in range(clifford.num_qubits): - # * make1forXkk(i) - # put a 1 one into position by permuting and using Hadamards(i,i) set_qubit_x_true(clifford_cpy, circuit, i) - # * .makeXrowzero(i) # make all entries in row i except ith equal to 0 # by using phase gate and CNOTS set_row_x_zero(clifford_cpy, circuit, i) - # * makeZrowzero(i) # treat Zs set_row_z_zero(clifford_cpy, circuit, i) diff --git a/releasenotes/notes/clifford-00645b805dedbb41.yaml b/releasenotes/notes/clifford-00645b805dedbb41.yaml index 331d5a38f84..8a7c27f8722 100644 --- a/releasenotes/notes/clifford-00645b805dedbb41.yaml +++ b/releasenotes/notes/clifford-00645b805dedbb41.yaml @@ -3,4 +3,4 @@ features: - | Adds :class:`qiskit.quantum_info.Clifford` operator class to the `quantum_info` module. This operator is an efficient symplectic - representation an N-qubit unitary operator form the Clifford group. + representation an N-qubit unitary operator from the Clifford group. From 9fc3e8e832e353e49064d8814b3e2f0f73c4f7fb Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 09:06:02 -0400 Subject: [PATCH 11/17] Add tests for to_matrix, to_instruction --- .../operators/symplectic/test_clifford.py | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 3aed603592b..68993985a4f 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -343,8 +343,8 @@ def test_2_qubit_relations(self): @ddt -class TestCliffordCircuits(QiskitTestCase): - """Stress tests for random clifford circuits.""" +class TestCliffordDecomposition(QiskitTestCase): + """Test Clifford decompositions.""" @combine(gates=[['h', 's'], ['h', 's', 'i', 'x', 'y', 'z'], ['h', 's', 'sdg'], ['h', 's', 'v'], ['h', 's', 'w'], ['h', 's', 'sdg', 'i', 'x', 'y', 'z', 'v', 'w']]) @@ -395,6 +395,62 @@ def test_to_operator_nqubit_gates(self, gates, num_qubits): target = Operator(circ) self.assertTrue(target.equiv(value)) + @combine(num_qubits=[1, 2, 3]) + def test_to_matrix(self, num_qubits): + """Test to_matrix method""" + samples = 10 + num_gates = 10 + seed = 333 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + mat = Clifford(circ).to_matrix() + self.assertIsInstance(mat, np.ndarray) + self.assertEqual(mat.shape, 2 * (2 ** num_qubits,)) + target = Operator(circ) + self.assertTrue(value.equiv(target)) + + @combine(num_qubits=[1, 2, 3]) + def test_to_circuit(self, num_qubits): + """Test to_circuit method""" + samples = 10 + num_gates = 10 + seed = 700 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + decomp = Clifford(circ).to_circuit() + self.assertIsInstance(decomp, QuantumCircuit) + self.assertEqual(decomp.num_qubits, circ.num_qubits) + value = Operator(decomp) + target = Operator(circ) + self.assertTrue(value.equiv(target)) + + @combine(num_qubits=[1, 2, 3]) + def test_to_instruction(self, num_qubits): + """Test to_instruction method""" + samples = 10 + num_gates = 10 + seed = 800 + gates = 'all' + for i in range(samples): + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + i) + decomp = Clifford(circ).to_instruction() + self.assertIsInstance(decomp, Gate) + self.assertEqual(decomp.num_qubits, circ.num_qubits) + value = Operator(decomp) + target = Operator(circ) + self.assertTrue(value.equiv(target)) + @ddt class TestCliffordOperators(QiskitTestCase): @@ -594,22 +650,6 @@ def test_dot_subsystem(self, num_qubits_1, num_qubits_2): target = Clifford(circ) self.assertEqual(target, value) - @combine(num_qubits=[1, 2, 3]) - def test_to_circuit(self, num_qubits): - """Test to_circuit method (decompose)""" - samples = 10 - num_gates = 10 - seed = 700 - gates = 'all' - for i in range(samples): - circ = random_clifford_circuit(num_qubits, - num_gates, - gates=gates, - seed=seed + i) - value = Clifford(Clifford(circ).to_circuit()).to_operator() - target = Operator(circ) - self.assertTrue(value.equiv(target)) - if __name__ == '__main__': unittest.main() From b19b8b38929bbd49cbcbf1bc3326bd2b1cd06be5 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 09:26:41 -0400 Subject: [PATCH 12/17] Add to_dict/from_dict tests --- .../operators/symplectic/test_clifford.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 68993985a4f..bce43ddea21 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -22,6 +22,7 @@ import numpy as np from qiskit.test import QiskitTestCase +from qiskit.exceptions import QiskitError from qiskit.circuit import Gate, QuantumRegister, QuantumCircuit from qiskit.extensions.standard import (IGate, XGate, YGate, ZGate, HGate, SGate, SdgGate, CXGate, CZGate, @@ -410,6 +411,7 @@ def test_to_matrix(self, num_qubits): mat = Clifford(circ).to_matrix() self.assertIsInstance(mat, np.ndarray) self.assertEqual(mat.shape, 2 * (2 ** num_qubits,)) + value = Operator(mat) target = Operator(circ) self.assertTrue(value.equiv(target)) @@ -650,6 +652,73 @@ def test_dot_subsystem(self, num_qubits_1, num_qubits_2): target = Clifford(circ) self.assertEqual(target, value) + def test_to_dict(self): + """Test to_dict method""" + + with self.subTest(msg="Identity"): + cliff = Clifford(np.eye(8)) + value = cliff.to_dict() + + keys_value = set(value.keys()) + keys_target = set(['destabilizer', 'stabilizer']) + self.assertEqual(keys_value, keys_target) + + stabilizer_value = set(value['stabilizer']) + stabilizer_target = set(['+IIIZ', '+IIZI', '+IZII', '+ZIII']) + self.assertEqual(stabilizer_value, stabilizer_target) + + destabilizer_value = set(value['destabilizer']) + destabilizer_target = set(['+IIIX', '+IIXI', '+IXII', '+XIII']) + self.assertEqual(destabilizer_value, destabilizer_target) + + with self.subTest(msg="bell"): + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + cliff = Clifford(qc) + value = cliff.to_dict() + + keys_value = set(value.keys()) + keys_target = set(['destabilizer', 'stabilizer']) + self.assertEqual(keys_value, keys_target) + + stabilizer_value = set(value['stabilizer']) + stabilizer_target = set(['+XX', '+ZZ']) + self.assertEqual(stabilizer_value, stabilizer_target) + + destabilizer_value = set(value['destabilizer']) + destabilizer_target = set(['+IZ', '+XI']) + self.assertEqual(destabilizer_value, destabilizer_target) + + def test_from_dict(self): + """Test from_dict method""" + + with self.subTest(msg='test raises not unitary'): + cliff_dict = { + "stabilizer": ['+XX', '+ZZ'], + "destabilizer": ['+IZ', '+IY']} + self.assertRaises(QiskitError, Clifford.from_dict, cliff_dict) + + with self.subTest(msg='test raises wrong shape'): + cliff_dict = { + "stabilizer": ['+XX', '+ZZ', '+YY'], + "destabilizer": ['+IZ', '+XI', '+IY']} + self.assertRaises(QiskitError, Clifford.from_dict, cliff_dict) + + @combine(num_qubits=[1, 2, 3, 4, 5]) + def test_dict_round_trip(self, num_qubits): + """Test round trip conversion to and from dict""" + num_gates = 10 + seed = 655 + gates = 'all' + circ = random_clifford_circuit(num_qubits, + num_gates, + gates=gates, + seed=seed + num_qubits) + target = Clifford(circ) + value = Clifford.from_dict(target.to_dict()) + self.assertEqual(value, target) + if __name__ == '__main__': unittest.main() From a61295d28f468c924f34c0b1ff65ea1c4919b423 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 16:17:22 -0400 Subject: [PATCH 13/17] Add underscore to private clifford funcs --- .../operators/symplectic/clifford_circuits.py | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py index cc23515b977..da6356758a4 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -44,13 +44,13 @@ def append_gate(clifford, gate, qargs=None): # Basis Clifford Gates basis_1q = { - 'i': append_i, 'id': append_i, 'iden': append_i, - 'x': append_x, 'y': append_y, 'z': append_z, 'h': append_h, - 's': append_s, 'sdg': append_sdg, 'sinv': append_sdg, - 'v': append_v, 'w': append_w + 'i': _append_i, 'id': _append_i, 'iden': _append_i, + 'x': _append_x, 'y': _append_y, 'z': _append_z, 'h': _append_h, + 's': _append_s, 'sdg': __append_sdg, 'sinv': __append_sdg, + 'v': _append_v, 'w': _append_w } basis_2q = { - 'cx': append_cx, 'cz': append_cz, 'swap': append_swap + 'cx': append_cx, 'cz': _append_cz, 'swap': _append_swap } # Non-clifford gates @@ -114,19 +114,19 @@ def decompose_clifford(clifford): for i in range(clifford.num_qubits): # put a 1 one into position by permuting and using Hadamards(i,i) - set_qubit_x_true(clifford_cpy, circuit, i) + _set_qubit_x_true(clifford_cpy, circuit, i) # make all entries in row i except ith equal to 0 # by using phase gate and CNOTS - set_row_x_zero(clifford_cpy, circuit, i) + _set_row_x_zero(clifford_cpy, circuit, i) # treat Zs - set_row_z_zero(clifford_cpy, circuit, i) + _set_row_z_zero(clifford_cpy, circuit, i) for i in range(clifford.num_qubits): if clifford_cpy.destabilizer.phase[i]: - append_z(clifford_cpy, i) + _append_z(clifford_cpy, i) circuit.z(i) if clifford_cpy.stabilizer.phase[i]: - append_x(clifford_cpy, i) + _append_x(clifford_cpy, i) circuit.x(i) # Next we invert the circuit to undo the row reduction and return the # result as a gate instruction @@ -137,7 +137,7 @@ def decompose_clifford(clifford): # Helper functions for applying basis gates # --------------------------------------------------------------------- -def append_i(clifford, qubit): +def _append_i(clifford, qubit): """Apply an I gate to a Clifford. Args: @@ -151,7 +151,7 @@ def append_i(clifford, qubit): return clifford -def append_x(clifford, qubit): +def _append_x(clifford, qubit): """Apply an X gate to a Clifford. Args: @@ -165,7 +165,7 @@ def append_x(clifford, qubit): return clifford -def append_y(clifford, qubit): +def _append_y(clifford, qubit): """Apply a Y gate to a Clifford. Args: @@ -181,7 +181,7 @@ def append_y(clifford, qubit): return clifford -def append_z(clifford, qubit): +def _append_z(clifford, qubit): """Apply an Z gate to a Clifford. Args: @@ -195,7 +195,7 @@ def append_z(clifford, qubit): return clifford -def append_h(clifford, qubit): +def _append_h(clifford, qubit): """Apply a H gate to a Clifford. Args: @@ -214,7 +214,7 @@ def append_h(clifford, qubit): return clifford -def append_s(clifford, qubit): +def _append_s(clifford, qubit): """Apply an S gate to a Clifford. Args: @@ -232,7 +232,7 @@ def append_s(clifford, qubit): return clifford -def append_sdg(clifford, qubit): +def __append_sdg(clifford, qubit): """Apply an Sdg gate to a Clifford. Args: @@ -249,7 +249,7 @@ def append_sdg(clifford, qubit): return clifford -def append_v(clifford, qubit): +def _append_v(clifford, qubit): """Apply a V gate to a Clifford. This is equivalent to an Sdg gate followed by a H gate. @@ -269,7 +269,7 @@ def append_v(clifford, qubit): return clifford -def append_w(clifford, qubit): +def _append_w(clifford, qubit): """Apply a W gate to a Clifford. This is equivalent to two V gates. @@ -310,7 +310,7 @@ def append_cx(clifford, control, target): return clifford -def append_cz(clifford, control, target): +def _append_cz(clifford, control, target): """Apply a CZ gate to a Clifford. Args: @@ -331,7 +331,7 @@ def append_cz(clifford, control, target): return clifford -def append_swap(clifford, qubit0, qubit1): +def _append_swap(clifford, qubit0, qubit1): """Apply a Swap gate to a Clifford. Args: @@ -351,7 +351,7 @@ def append_swap(clifford, qubit0, qubit1): # Helper functions for decomposition # --------------------------------------------------------------------- -def set_qubit_x_true(clifford, circuit, qubit): +def _set_qubit_x_true(clifford, circuit, qubit): """Set destabilizer.X[qubit, qubit] to be True. This is done by permuting columns l > qubit or if necessary applying @@ -366,22 +366,22 @@ def set_qubit_x_true(clifford, circuit, qubit): # Try to find non-zero element for i in range(qubit + 1, clifford.num_qubits): if x[i]: - append_swap(clifford, i, qubit) + _append_swap(clifford, i, qubit) circuit.swap(i, qubit) return # no non-zero element found: need to apply Hadamard somewhere for i in range(qubit, clifford.num_qubits): if z[i]: - append_h(clifford, i) + _append_h(clifford, i) circuit.h(i) if i != qubit: - append_swap(clifford, i, qubit) + _append_swap(clifford, i, qubit) circuit.swap(i, qubit) return -def set_row_x_zero(clifford, circuit, qubit): +def _set_row_x_zero(clifford, circuit, qubit): """Set destabilizer.X[qubit, i] to False for all i > qubit. This is done by applying CNOTS assumes k<=N and A[k][k]=1 @@ -399,7 +399,7 @@ def set_row_x_zero(clifford, circuit, qubit): if np.any(z[qubit:]): if not z[qubit]: # to treat Zs: make sure row.Z[k] to True - append_s(clifford, qubit) + _append_s(clifford, qubit) circuit.s(qubit) # reverse CNOTS @@ -408,15 +408,15 @@ def set_row_x_zero(clifford, circuit, qubit): append_cx(clifford, i, qubit) circuit.cx(i, qubit) # set row.Z[qubit] to False - append_s(clifford, qubit) + _append_s(clifford, qubit) circuit.s(qubit) -def set_row_z_zero(clifford, circuit, qubit): +def _set_row_z_zero(clifford, circuit, qubit): """Set stabilizer.Z[qubit, i] to False for all i > qubit. Implemented by applying (reverse) CNOTS assumes qubit < num_qubits - and set_row_x_zero has been called first + and _set_row_x_zero has been called first """ x = clifford.stabilizer.X[qubit] @@ -431,14 +431,14 @@ def set_row_z_zero(clifford, circuit, qubit): # check whether Xs need to be set to zero: if np.any(x[qubit:]): - append_h(clifford, qubit) + _append_h(clifford, qubit) circuit.h(qubit) for i in range(qubit + 1, clifford.num_qubits): if x[i]: append_cx(clifford, qubit, i) circuit.cx(qubit, i) if z[qubit]: - append_s(clifford, qubit) + _append_s(clifford, qubit) circuit.s(qubit) - append_h(clifford, qubit) + _append_h(clifford, qubit) circuit.h(qubit) From c825454f2e1b754344294fee19f3b11eec77cb3d Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 16:29:45 -0400 Subject: [PATCH 14/17] Rename `append_gate` to `_append_circuit` --- .../operators/symplectic/clifford.py | 13 ++- .../operators/symplectic/clifford_circuits.py | 89 ++++++++++--------- .../operators/symplectic/test_clifford.py | 80 ++++++++--------- 3 files changed, 98 insertions(+), 84 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index fd6db03b5aa..fa688f1e8fa 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -24,7 +24,7 @@ from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.scalar_op import ScalarOp from .stabilizer_table import StabilizerTable -from .clifford_circuits import append_gate, decompose_clifford +from .clifford_circuits import _append_circuit, decompose_clifford class Clifford(BaseOperator): @@ -249,6 +249,15 @@ def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, 'qargs', None) + # If other is a QuantumCircuit we can more efficiently compose + # using the _append_circuit method to update each gate recursively + # to the current Clifford, rather than converting to a Clifford first + # and then doing the composition of tables. + if not front and isinstance(other, (QuantumCircuit, Instruction)): + ret = self.copy() + _append_circuit(ret, other, qargs=qargs) + return ret + if not isinstance(other, Clifford): other = Clifford(other) @@ -359,7 +368,7 @@ def from_instruction(instruction): # Initialize an identity Clifford clifford = Clifford(np.eye(2 * instruction.num_qubits), validate=False) - append_gate(clifford, instruction) + _append_circuit(clifford, instruction) return clifford # --------------------------------------------------------------------- diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py index da6356758a4..3943ff69bfe 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -25,12 +25,50 @@ # Main functions # --------------------------------------------------------------------- -def append_gate(clifford, gate, qargs=None): - """Update Clifford inplace by applying a Clifford gate. +def decompose_clifford(clifford): + """Decompose a Clifford into a QuantumCircuit. + + Args: + clifford (Clifford): a clifford operator. + + Return: + QuantumCircuit: a circuit implementation of the Clifford. + """ + # Compose a circuit which we will convert to an instruction + circuit = QuantumCircuit(clifford.num_qubits, + name=str(clifford)) + + # Make a copy of Clifford as we are going to do row reduction to + # reduce it to an identity + clifford_cpy = clifford.copy() + + for i in range(clifford.num_qubits): + # put a 1 one into position by permuting and using Hadamards(i,i) + _set_qubit_x_true(clifford_cpy, circuit, i) + # make all entries in row i except ith equal to 0 + # by using phase gate and CNOTS + _set_row_x_zero(clifford_cpy, circuit, i) + # treat Zs + _set_row_z_zero(clifford_cpy, circuit, i) + + for i in range(clifford.num_qubits): + if clifford_cpy.destabilizer.phase[i]: + _append_z(clifford_cpy, i) + circuit.z(i) + if clifford_cpy.stabilizer.phase[i]: + _append_x(clifford_cpy, i) + circuit.x(i) + # Next we invert the circuit to undo the row reduction and return the + # result as a gate instruction + return circuit.inverse() + + +def _append_circuit(clifford, circuit, qargs=None): + """Update Clifford inplace by applying a Clifford circuit. Args: clifford (Clifford): the Clifford to update. - gate (Gate or str): the gate or composite gate to apply. + gate (QuantumCircuit or Instruction): the gate or composite gate to apply. qargs (list or None): The qubits to apply gate to. Returns: @@ -42,6 +80,11 @@ def append_gate(clifford, gate, qargs=None): if qargs is None: qargs = list(range(clifford.num_qubits)) + if isinstance(circuit, QuantumCircuit): + gate = circuit.to_instruction() + else: + gate = circuit + # Basis Clifford Gates basis_1q = { 'i': _append_i, 'id': _append_i, 'iden': _append_i, @@ -91,48 +134,10 @@ def append_gate(clifford, gate, qargs=None): instr.name)) # Get the integer position of the flat register new_qubits = [qargs[tup.index] for tup in qregs] - append_gate(clifford, instr, new_qubits) + _append_circuit(clifford, instr, new_qubits) return clifford -def decompose_clifford(clifford): - """Decompose a Clifford into a QuantumCircuit. - - Args: - clifford (Clifford): a clifford operator. - - Return: - QuantumCircuit: a circuit implementation of the Clifford. - """ - # Compose a circuit which we will convert to an instruction - circuit = QuantumCircuit(clifford.num_qubits, - name=str(clifford)) - - # Make a copy of Clifford as we are going to do row reduction to - # reduce it to an identity - clifford_cpy = clifford.copy() - - for i in range(clifford.num_qubits): - # put a 1 one into position by permuting and using Hadamards(i,i) - _set_qubit_x_true(clifford_cpy, circuit, i) - # make all entries in row i except ith equal to 0 - # by using phase gate and CNOTS - _set_row_x_zero(clifford_cpy, circuit, i) - # treat Zs - _set_row_z_zero(clifford_cpy, circuit, i) - - for i in range(clifford.num_qubits): - if clifford_cpy.destabilizer.phase[i]: - _append_z(clifford_cpy, i) - circuit.z(i) - if clifford_cpy.stabilizer.phase[i]: - _append_x(clifford_cpy, i) - circuit.x(i) - # Next we invert the circuit to undo the row reduction and return the - # result as a gate instruction - return circuit.inverse() - - # --------------------------------------------------------------------- # Helper functions for applying basis gates # --------------------------------------------------------------------- diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index bce43ddea21..2d0eb8ec553 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -28,7 +28,7 @@ SGate, SdgGate, CXGate, CZGate, SwapGate) from qiskit.quantum_info.operators import Clifford, Operator -from qiskit.quantum_info.operators.symplectic.clifford_circuits import append_gate +from qiskit.quantum_info.operators.symplectic.clifford_circuits import _append_circuit class VGate(Gate): @@ -164,7 +164,7 @@ def test_append_1_qubit_gate(self): "v", "w"): with self.subTest(msg='append gate %s' % gate_name): cliff = Clifford([[1, 0], [0, 1]]) - cliff = append_gate(cliff, gate_name, [0]) + cliff = _append_circuit(cliff, gate_name, [0]) value_table = cliff.table._array value_phase = cliff.table._phase value_stabilizer = cliff.stabilizer.to_labels() @@ -189,8 +189,8 @@ def test_1_qubit_identity_relations(self): with self.subTest(msg='identity for gate %s' % gate_name): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() - cliff = append_gate(cliff, gate_name, [0]) - cliff = append_gate(cliff, gate_name, [0]) + cliff = _append_circuit(cliff, gate_name, [0]) + cliff = _append_circuit(cliff, gate_name, [0]) self.assertEqual(cliff, cliff1) gates = ['s', 's', 'v'] @@ -200,8 +200,8 @@ def test_1_qubit_identity_relations(self): with self.subTest(msg='identity for gate %s' % gate_name): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() - cliff = append_gate(cliff, gate_name, [0]) - cliff = append_gate(cliff, inv_gate, [0]) + cliff = _append_circuit(cliff, gate_name, [0]) + cliff = _append_circuit(cliff, inv_gate, [0]) self.assertEqual(cliff, cliff1) def test_1_qubit_mult_relations(self): @@ -217,9 +217,9 @@ def test_1_qubit_mult_relations(self): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() - cliff = append_gate(cliff, split_rel[0], [0]) - cliff = append_gate(cliff, split_rel[2], [0]) - cliff1 = append_gate(cliff1, split_rel[4], [0]) + cliff = _append_circuit(cliff, split_rel[0], [0]) + cliff = _append_circuit(cliff, split_rel[2], [0]) + cliff1 = _append_circuit(cliff1, split_rel[4], [0]) self.assertEqual(cliff, cliff1) def test_1_qubit_conj_relations(self): @@ -235,10 +235,10 @@ def test_1_qubit_conj_relations(self): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() - cliff = append_gate(cliff, split_rel[0], [0]) - cliff = append_gate(cliff, split_rel[2], [0]) - cliff = append_gate(cliff, split_rel[4], [0]) - cliff1 = append_gate(cliff1, split_rel[6], [0]) + cliff = _append_circuit(cliff, split_rel[0], [0]) + cliff = _append_circuit(cliff, split_rel[2], [0]) + cliff = _append_circuit(cliff, split_rel[4], [0]) + cliff1 = _append_circuit(cliff1, split_rel[6], [0]) self.assertEqual(cliff, cliff1) @combine(gate_name=("cx", "cz", "swap"), qubits=([0, 1], [1, 0])) @@ -273,7 +273,7 @@ def test_append_2_qubit_gate(self, gate_name, qubits): } gate_qubits = gate_name + " " + str(qubits) - cliff = append_gate(Clifford(np.eye(4)), gate_name, qubits) + cliff = _append_circuit(Clifford(np.eye(4)), gate_name, qubits) target = targets_cliffords[gate_qubits] self.assertEqual(target, cliff) @@ -286,8 +286,8 @@ def test_2_qubit_identity_relations(self): (gate_name, qubits)): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, gate_name, qubits) - cliff = append_gate(cliff, gate_name, qubits) + cliff = _append_circuit(cliff, gate_name, qubits) + cliff = _append_circuit(cliff, gate_name, qubits) self.assertEqual(cliff, cliff1) def test_2_qubit_relations(self): @@ -296,50 +296,50 @@ def test_2_qubit_relations(self): with self.subTest(msg='relation between cx, h and cz'): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, 'h', [1]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'h', [1]) - cliff = append_gate(cliff, 'cz', [0, 1]) + cliff = _append_circuit(cliff, 'h', [1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'h', [1]) + cliff = _append_circuit(cliff, 'cz', [0, 1]) self.assertEqual(cliff, cliff1) with self.subTest(msg='relation between cx and swap'): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'cx', [1, 0]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'swap', [0, 1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'cx', [1, 0]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'swap', [0, 1]) self.assertEqual(cliff, cliff1) with self.subTest(msg='relation between cx and x'): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'x', [0]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'x', [0]) - cliff = append_gate(cliff, 'x', [1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'x', [0]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'x', [0]) + cliff = _append_circuit(cliff, 'x', [1]) self.assertEqual(cliff, cliff1) with self.subTest(msg='relation between cx and z'): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'z', [1]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'z', [0]) - cliff = append_gate(cliff, 'z', [1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'z', [1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'z', [0]) + cliff = _append_circuit(cliff, 'z', [1]) self.assertEqual(cliff, cliff1) with self.subTest(msg='relation between cx and s'): cliff = Clifford(np.eye(4)) cliff1 = cliff.copy() - cliff = append_gate(cliff, 'cx', [1, 0]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 's', [1]) - cliff = append_gate(cliff, 'cx', [0, 1]) - cliff = append_gate(cliff, 'cx', [1, 0]) - cliff = append_gate(cliff, 'sdg', [0]) + cliff = _append_circuit(cliff, 'cx', [1, 0]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 's', [1]) + cliff = _append_circuit(cliff, 'cx', [0, 1]) + cliff = _append_circuit(cliff, 'cx', [1, 0]) + cliff = _append_circuit(cliff, 'sdg', [0]) self.assertEqual(cliff, cliff1) From 2c75f487ea681299a82ed18a9cd16cda5119595b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 16:30:03 -0400 Subject: [PATCH 15/17] Rename from_instruction to from_circuit --- .../operators/symplectic/clifford.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index fa688f1e8fa..ca91897b85c 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -118,7 +118,7 @@ def __init__(self, data, validate=True): # Initialize from a QuantumCircuit or Instruction object elif isinstance(data, (QuantumCircuit, Instruction)): - self._table = Clifford.from_instruction(data)._table + self._table = Clifford.from_circuit(data)._table # Initialize StabilizerTable directly from the data else: @@ -345,11 +345,11 @@ def to_instruction(self): return self.to_circuit().to_gate() @staticmethod - def from_instruction(instruction): + def from_circuit(circuit): """Initialize from a QuantumCircuit or Instruction. Args: - instruction (QuantumCircuit or ~qiskit.circuit.Instruction): + circuit (QuantumCircuit or ~qiskit.circuit.Instruction): instruction to initialize. Returns: @@ -359,16 +359,16 @@ def from_instruction(instruction): QiskitError: if the input instruction is non-Clifford or contains classical register instruction. """ - if not isinstance(instruction, (QuantumCircuit, Instruction)): + if not isinstance(circuit, (QuantumCircuit, Instruction)): raise QiskitError("Input must be a QuantumCircuit or Instruction") # Convert circuit to an instruction - if isinstance(instruction, QuantumCircuit): - instruction = instruction.to_instruction() + if isinstance(circuit, QuantumCircuit): + circuit = circuit.to_instruction() # Initialize an identity Clifford - clifford = Clifford(np.eye(2 * instruction.num_qubits), validate=False) - _append_circuit(clifford, instruction) + clifford = Clifford(np.eye(2 * circuit.num_qubits), validate=False) + _append_circuit(clifford, circuit) return clifford # --------------------------------------------------------------------- From 2f25fdcd25521a1d382f5c3152a3bc58c22040d8 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 16:48:52 -0400 Subject: [PATCH 16/17] lint --- qiskit/quantum_info/operators/symplectic/clifford_circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py index 3943ff69bfe..d32b999f0d8 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -68,7 +68,7 @@ def _append_circuit(clifford, circuit, qargs=None): Args: clifford (Clifford): the Clifford to update. - gate (QuantumCircuit or Instruction): the gate or composite gate to apply. + circuit (QuantumCircuit or Instruction): the gate or composite gate to apply. qargs (list or None): The qubits to apply gate to. Returns: From f20777a8fed49b3dcaf1c4fcf895ee4004c98f54 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 1 Apr 2020 16:57:30 -0400 Subject: [PATCH 17/17] move decompose_clifford to synethsis --- .../operators/symplectic/clifford.py | 3 +- .../operators/symplectic/clifford_circuits.py | 146 +--------------- .../synthesis/clifford_decompose.py | 157 ++++++++++++++++++ 3 files changed, 162 insertions(+), 144 deletions(-) create mode 100644 qiskit/quantum_info/synthesis/clifford_decompose.py diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index ca91897b85c..44962876f09 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -23,8 +23,9 @@ from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.scalar_op import ScalarOp +from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford from .stabilizer_table import StabilizerTable -from .clifford_circuits import _append_circuit, decompose_clifford +from .clifford_circuits import _append_circuit class Clifford(BaseOperator): diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py index d32b999f0d8..4a77023feb6 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py +++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py @@ -12,57 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -Circuit methods for Clifford class. +Circuit simulation for the Clifford class. """ # pylint: disable=invalid-name -import numpy as np from qiskit.exceptions import QiskitError from qiskit.circuit import QuantumCircuit -# --------------------------------------------------------------------- -# Main functions -# --------------------------------------------------------------------- - -def decompose_clifford(clifford): - """Decompose a Clifford into a QuantumCircuit. - - Args: - clifford (Clifford): a clifford operator. - - Return: - QuantumCircuit: a circuit implementation of the Clifford. - """ - # Compose a circuit which we will convert to an instruction - circuit = QuantumCircuit(clifford.num_qubits, - name=str(clifford)) - - # Make a copy of Clifford as we are going to do row reduction to - # reduce it to an identity - clifford_cpy = clifford.copy() - - for i in range(clifford.num_qubits): - # put a 1 one into position by permuting and using Hadamards(i,i) - _set_qubit_x_true(clifford_cpy, circuit, i) - # make all entries in row i except ith equal to 0 - # by using phase gate and CNOTS - _set_row_x_zero(clifford_cpy, circuit, i) - # treat Zs - _set_row_z_zero(clifford_cpy, circuit, i) - - for i in range(clifford.num_qubits): - if clifford_cpy.destabilizer.phase[i]: - _append_z(clifford_cpy, i) - circuit.z(i) - if clifford_cpy.stabilizer.phase[i]: - _append_x(clifford_cpy, i) - circuit.x(i) - # Next we invert the circuit to undo the row reduction and return the - # result as a gate instruction - return circuit.inverse() - - def _append_circuit(clifford, circuit, qargs=None): """Update Clifford inplace by applying a Clifford circuit. @@ -93,7 +50,7 @@ def _append_circuit(clifford, circuit, qargs=None): 'v': _append_v, 'w': _append_w } basis_2q = { - 'cx': append_cx, 'cz': _append_cz, 'swap': _append_swap + 'cx': _append_cx, 'cz': _append_cz, 'swap': _append_swap } # Non-clifford gates @@ -294,7 +251,7 @@ def _append_w(clifford, qubit): return clifford -def append_cx(clifford, control, target): +def _append_cx(clifford, control, target): """Apply a CX gate to a Clifford. Args: @@ -350,100 +307,3 @@ def _append_swap(clifford, qubit0, qubit1): clifford.table.X[:, [qubit0, qubit1]] = clifford.table.X[:, [qubit1, qubit0]] clifford.table.Z[:, [qubit0, qubit1]] = clifford.table.Z[:, [qubit1, qubit0]] return clifford - - -# --------------------------------------------------------------------- -# Helper functions for decomposition -# --------------------------------------------------------------------- - -def _set_qubit_x_true(clifford, circuit, qubit): - """Set destabilizer.X[qubit, qubit] to be True. - - This is done by permuting columns l > qubit or if necessary applying - a Hadamard - """ - x = clifford.destabilizer.X[qubit] - z = clifford.destabilizer.Z[qubit] - - if x[qubit]: - return - - # Try to find non-zero element - for i in range(qubit + 1, clifford.num_qubits): - if x[i]: - _append_swap(clifford, i, qubit) - circuit.swap(i, qubit) - return - - # no non-zero element found: need to apply Hadamard somewhere - for i in range(qubit, clifford.num_qubits): - if z[i]: - _append_h(clifford, i) - circuit.h(i) - if i != qubit: - _append_swap(clifford, i, qubit) - circuit.swap(i, qubit) - return - - -def _set_row_x_zero(clifford, circuit, qubit): - """Set destabilizer.X[qubit, i] to False for all i > qubit. - - This is done by applying CNOTS assumes k<=N and A[k][k]=1 - """ - x = clifford.destabilizer.X[qubit] - z = clifford.destabilizer.Z[qubit] - - # Check X first - for i in range(qubit + 1, clifford.num_qubits): - if x[i]: - append_cx(clifford, qubit, i) - circuit.cx(qubit, i) - - # Check whether Zs need to be set to zero: - if np.any(z[qubit:]): - if not z[qubit]: - # to treat Zs: make sure row.Z[k] to True - _append_s(clifford, qubit) - circuit.s(qubit) - - # reverse CNOTS - for i in range(qubit + 1, clifford.num_qubits): - if z[i]: - append_cx(clifford, i, qubit) - circuit.cx(i, qubit) - # set row.Z[qubit] to False - _append_s(clifford, qubit) - circuit.s(qubit) - - -def _set_row_z_zero(clifford, circuit, qubit): - """Set stabilizer.Z[qubit, i] to False for all i > qubit. - - Implemented by applying (reverse) CNOTS assumes qubit < num_qubits - and _set_row_x_zero has been called first - """ - - x = clifford.stabilizer.X[qubit] - z = clifford.stabilizer.Z[qubit] - - # check whether Zs need to be set to zero: - if np.any(z[qubit + 1:]): - for i in range(qubit + 1, clifford.num_qubits): - if z[i]: - append_cx(clifford, i, qubit) - circuit.cx(i, qubit) - - # check whether Xs need to be set to zero: - if np.any(x[qubit:]): - _append_h(clifford, qubit) - circuit.h(qubit) - for i in range(qubit + 1, clifford.num_qubits): - if x[i]: - append_cx(clifford, qubit, i) - circuit.cx(qubit, i) - if z[qubit]: - _append_s(clifford, qubit) - circuit.s(qubit) - _append_h(clifford, qubit) - circuit.h(qubit) diff --git a/qiskit/quantum_info/synthesis/clifford_decompose.py b/qiskit/quantum_info/synthesis/clifford_decompose.py new file mode 100644 index 00000000000..706ff8a11eb --- /dev/null +++ b/qiskit/quantum_info/synthesis/clifford_decompose.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020 +# +# 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. +""" +Circuit synthesis for the Clifford class. +""" +# pylint: disable=invalid-name + +import numpy as np +from qiskit.circuit import QuantumCircuit +from qiskit.quantum_info.operators.symplectic.clifford_circuits import ( + _append_z, _append_x, _append_h, _append_s, _append_cx, _append_swap) + + +def decompose_clifford(clifford): + """Decompose a Clifford operator into a QuantumCircuit. + + Args: + clifford (Clifford): a clifford operator. + + Return: + QuantumCircuit: a circuit implementation of the Clifford. + """ + # Compose a circuit which we will convert to an instruction + circuit = QuantumCircuit(clifford.num_qubits, + name=str(clifford)) + + # Make a copy of Clifford as we are going to do row reduction to + # reduce it to an identity + clifford_cpy = clifford.copy() + + for i in range(clifford.num_qubits): + # put a 1 one into position by permuting and using Hadamards(i,i) + _set_qubit_x_true(clifford_cpy, circuit, i) + # make all entries in row i except ith equal to 0 + # by using phase gate and CNOTS + _set_row_x_zero(clifford_cpy, circuit, i) + # treat Zs + _set_row_z_zero(clifford_cpy, circuit, i) + + for i in range(clifford.num_qubits): + if clifford_cpy.destabilizer.phase[i]: + _append_z(clifford_cpy, i) + circuit.z(i) + if clifford_cpy.stabilizer.phase[i]: + _append_x(clifford_cpy, i) + circuit.x(i) + # Next we invert the circuit to undo the row reduction and return the + # result as a gate instruction + return circuit.inverse() + + +# --------------------------------------------------------------------- +# Helper functions for decomposition +# --------------------------------------------------------------------- + +def _set_qubit_x_true(clifford, circuit, qubit): + """Set destabilizer.X[qubit, qubit] to be True. + + This is done by permuting columns l > qubit or if necessary applying + a Hadamard + """ + x = clifford.destabilizer.X[qubit] + z = clifford.destabilizer.Z[qubit] + + if x[qubit]: + return + + # Try to find non-zero element + for i in range(qubit + 1, clifford.num_qubits): + if x[i]: + _append_swap(clifford, i, qubit) + circuit.swap(i, qubit) + return + + # no non-zero element found: need to apply Hadamard somewhere + for i in range(qubit, clifford.num_qubits): + if z[i]: + _append_h(clifford, i) + circuit.h(i) + if i != qubit: + _append_swap(clifford, i, qubit) + circuit.swap(i, qubit) + return + + +def _set_row_x_zero(clifford, circuit, qubit): + """Set destabilizer.X[qubit, i] to False for all i > qubit. + + This is done by applying CNOTS assumes k<=N and A[k][k]=1 + """ + x = clifford.destabilizer.X[qubit] + z = clifford.destabilizer.Z[qubit] + + # Check X first + for i in range(qubit + 1, clifford.num_qubits): + if x[i]: + _append_cx(clifford, qubit, i) + circuit.cx(qubit, i) + + # Check whether Zs need to be set to zero: + if np.any(z[qubit:]): + if not z[qubit]: + # to treat Zs: make sure row.Z[k] to True + _append_s(clifford, qubit) + circuit.s(qubit) + + # reverse CNOTS + for i in range(qubit + 1, clifford.num_qubits): + if z[i]: + _append_cx(clifford, i, qubit) + circuit.cx(i, qubit) + # set row.Z[qubit] to False + _append_s(clifford, qubit) + circuit.s(qubit) + + +def _set_row_z_zero(clifford, circuit, qubit): + """Set stabilizer.Z[qubit, i] to False for all i > qubit. + + Implemented by applying (reverse) CNOTS assumes qubit < num_qubits + and _set_row_x_zero has been called first + """ + + x = clifford.stabilizer.X[qubit] + z = clifford.stabilizer.Z[qubit] + + # check whether Zs need to be set to zero: + if np.any(z[qubit + 1:]): + for i in range(qubit + 1, clifford.num_qubits): + if z[i]: + _append_cx(clifford, i, qubit) + circuit.cx(i, qubit) + + # check whether Xs need to be set to zero: + if np.any(x[qubit:]): + _append_h(clifford, qubit) + circuit.h(qubit) + for i in range(qubit + 1, clifford.num_qubits): + if x[i]: + _append_cx(clifford, qubit, i) + circuit.cx(qubit, i) + if z[qubit]: + _append_s(clifford, qubit) + circuit.s(qubit) + _append_h(clifford, qubit) + circuit.h(qubit)