From bb9ad014dbd929d540f35e39863a6ddf8ad8a06f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 30 Apr 2023 14:54:23 +0300 Subject: [PATCH 01/18] contructing linear functions from circuits with barriers and delays; adding __eq__ method --- .../generalized_gates/linear_function.py | 6 ++ ...-functions-usability-45265f293a80a6e5.yaml | 9 +++ .../circuit/library/test_linear_function.py | 55 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index f1a3c25c1ee0..26322839e338 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -120,6 +120,10 @@ def __init__( name="linear_function", num_qubits=len(linear), params=[linear, original_circuit] ) + def __eq__(self, other): + """Check if two linear functions represent the same matrix.""" + return (self.linear == other.linear).all() + def validate_parameter(self, parameter): """Parameter validation""" return parameter @@ -186,6 +190,8 @@ def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): cb = qc.find_bit(instruction.qubits[0]).index tb = qc.find_bit(instruction.qubits[1]).index mat[[cb, tb]] = mat[[tb, cb]] + elif instruction.operation.name in ("barrier", "delay"): + continue else: raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") diff --git a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml new file mode 100644 index 000000000000..6613a1392b12 --- /dev/null +++ b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added :meth:`.LinearFunction.__eq__` method. Two objects of type :class:`.LinearFunction` + are considered equal when their representations as binary invertible matrices are equal. + - | + Allowing to construct a :class:`.LinearFunction` from a quantum circuit with + :class:`~qiskit.circuit.Barrier` and :class:`~qiskit.circuit.Delay` instructions. + diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index f9b7e5dce21e..041a0825c786 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -239,6 +239,61 @@ def test_no_original_definition(self): linear_function = LinearFunction(mat) self.assertIsNone(linear_function.original_circuit) + def test_barriers(self): + """Test constructing linear functions from circuits with barriers.""" + linear_circuit_1 = QuantumCircuit(4) + linear_circuit_1.cx(0, 1) + linear_circuit_1.cx(1, 2) + linear_circuit_1.cx(2, 3) + linear_function_1 = LinearFunction(linear_circuit_1) + + linear_circuit_2 = QuantumCircuit(4) + linear_circuit_2.barrier() + linear_circuit_2.cx(0, 1) + linear_circuit_2.cx(1, 2) + linear_circuit_2.barrier() + linear_circuit_2.cx(2, 3) + linear_circuit_2.barrier() + linear_function_2 = LinearFunction(linear_circuit_2) + + self.assertTrue(np.all(linear_function_1.linear == linear_function_2.linear)) + self.assertEqual(linear_function_1, linear_function_2) + + def test_delays(self): + """Test constructing linear functions from circuits with delays.""" + linear_circuit_1 = QuantumCircuit(4) + linear_circuit_1.cx(0, 1) + linear_circuit_1.cx(1, 2) + linear_circuit_1.cx(2, 3) + linear_function_1 = LinearFunction(linear_circuit_1) + + linear_circuit_2 = QuantumCircuit(4) + linear_circuit_2.delay(500, 1) + linear_circuit_2.cx(0, 1) + linear_circuit_2.cx(1, 2) + linear_circuit_2.delay(100, 0) + linear_circuit_2.cx(2, 3) + linear_circuit_2.delay(200, 2) + linear_function_2 = LinearFunction(linear_circuit_2) + + self.assertTrue(np.all(linear_function_1.linear == linear_function_2.linear)) + self.assertEqual(linear_function_1, linear_function_2) + + def test_eq(self): + """Test that checking equality between two linear functions only depends on matrices.""" + linear_circuit_1 = QuantumCircuit(3) + linear_circuit_1.cx(0, 1) + linear_circuit_1.cx(0, 2) + linear_function_1 = LinearFunction(linear_circuit_1) + + linear_circuit_2 = QuantumCircuit(3) + linear_circuit_2.cx(0, 2) + linear_circuit_2.cx(0, 1) + linear_function_2 = LinearFunction(linear_circuit_1) + + self.assertTrue(np.all(linear_function_1.linear == linear_function_2.linear)) + self.assertEqual(linear_function_1, linear_function_2) + if __name__ == "__main__": unittest.main() From 3387d6983b6ac5105c6dc58e0dc8d88bdd48e0dc Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 30 Apr 2023 21:27:53 +0300 Subject: [PATCH 02/18] adding linear function extend_with_identity method --- .../generalized_gates/linear_function.py | 23 +++++++++++++++++++ ...-functions-usability-45265f293a80a6e5.yaml | 4 ++++ .../circuit/library/test_linear_function.py | 16 +++++++++++++ 3 files changed, 43 insertions(+) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 26322839e338..402329e50bd4 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -175,6 +175,26 @@ def permutation_pattern(self): locs = np.where(linear == 1) return locs[1] + def extend_with_identity(self, nq: int, positions: list[int]) -> LinearFunction: + """Extend linear function to a linear function over nq qubits, + with identities on other subsystems. + + Args: + nq: number of qubits of the extended function. + + positions: describes the positions of original qubits in the extended + function's qubits. + + Returns: + LinearFunction: extended linear function. + """ + extended_mat = np.eye(nq, dtype=bool) + + for i, pos in enumerate(positions): + extended_mat[positions, pos] = self.linear[:, i] + + return LinearFunction(extended_mat) + def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): """This creates a n x n matrix corresponding to the given linear quantum circuit.""" @@ -192,6 +212,9 @@ def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): mat[[cb, tb]] = mat[[tb, cb]] elif instruction.operation.name in ("barrier", "delay"): continue + elif getattr(instruction.operation, "definition", None) is not None: + raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") + else: raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") diff --git a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml index 6613a1392b12..9eff00af0e50 100644 --- a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml +++ b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml @@ -3,6 +3,10 @@ features: - | Added :meth:`.LinearFunction.__eq__` method. Two objects of type :class:`.LinearFunction` are considered equal when their representations as binary invertible matrices are equal. + - | + Added :meth:`.LinearFunction.extend_with_identity` method, which allows to extend + a linear function over ``k`` qubits to a linear function over ``n >= k`` qubits, + with identities on other subsystems. - | Allowing to construct a :class:`.LinearFunction` from a quantum circuit with :class:`~qiskit.circuit.Barrier` and :class:`~qiskit.circuit.Delay` instructions. diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index 041a0825c786..f3e246ab2cbb 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -294,6 +294,22 @@ def test_eq(self): self.assertTrue(np.all(linear_function_1.linear == linear_function_2.linear)) self.assertEqual(linear_function_1, linear_function_2) + def test_extend_with_identity(self): + """Test extending linear function with identity.""" + lf = LinearFunction([[1, 1, 1], [0, 1, 1], [0, 0, 1]]) + + extended1 = lf.extend_with_identity(4, [0, 1, 2]) + expected1 = LinearFunction([[1, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + self.assertEqual(extended1, expected1) + + extended2 = lf.extend_with_identity(4, [1, 2, 3]) + expected2 = LinearFunction([[1, 0, 0, 0], [0, 1, 1, 1], [0, 0, 1, 1], [0, 0, 0, 1]]) + self.assertEqual(extended2, expected2) + + extended3 = lf.extend_with_identity(4, [3, 2, 1]) + expected3 = LinearFunction([[1, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1]]) + self.assertEqual(extended3, expected3) + if __name__ == "__main__": unittest.main() From 551d76302c9b95bef8705c1e294dae40945c0961 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 07:35:11 +0300 Subject: [PATCH 03/18] constructing linear functions from nested circuits --- .../generalized_gates/linear_function.py | 11 ++++++++--- .../circuit/library/test_linear_function.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 402329e50bd4..24a167784cf9 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -212,9 +212,14 @@ def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): mat[[cb, tb]] = mat[[tb, cb]] elif instruction.operation.name in ("barrier", "delay"): continue - elif getattr(instruction.operation, "definition", None) is not None: - raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") - + elif getattr(instruction.qoperation, "definition", None) is not None: + # Iteratively construct linear function for the operation, and + # compose (multiply) linear matrices. + other = LinearFunction(instruction.operation.definition) + positions = [qc.find_bit(q).index for q in instruction.qubits] + other = other.extend_with_identity(len(mat), positions) + mat = np.dot(other.linear.astype(int), mat.astype(int)) % 2 + mat = mat.astype(bool) else: raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index f3e246ab2cbb..2bdee2a88b88 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -310,6 +310,24 @@ def test_extend_with_identity(self): expected3 = LinearFunction([[1, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1]]) self.assertEqual(extended3, expected3) + def test_from_nested_quantum_circuit(self): + """Test constructing a linear function from a quantum circuit with + nested linear quantum circuits.""" + + qc1 = QuantumCircuit(3) + qc1.swap(1, 2) + + qc2 = QuantumCircuit(3) + qc2.append(qc1, [2, 1, 0]) # swaps 0 and 1 + qc2.swap(1, 2) # cycles 0->2->1->0 + + qc3 = QuantumCircuit(4) + qc3.append(qc2, [0, 1, 3]) # cycles 0->3->1->0, 2 untouched + + linear_function = LinearFunction(qc3) + expected = LinearFunction([[0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0]]) + self.assertEquals(linear_function, expected) + if __name__ == "__main__": unittest.main() From 5f55883408f409e0fa8931425f49f7ef874f02da Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 09:55:43 +0300 Subject: [PATCH 04/18] reorganizing code; constructing linear functions from cliffords (when possible) --- .../generalized_gates/linear_function.py | 129 ++++++++++++------ 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 24a167784cf9..9301f2029754 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -14,8 +14,9 @@ from __future__ import annotations import numpy as np -from qiskit.circuit import QuantumCircuit, Gate +from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError +from qiskit.quantum_info import Clifford from qiskit.synthesis.linear import check_invertible_binary_matrix @@ -83,18 +84,11 @@ def __init__( or a quantum circuit contains non-linear gates. """ - if not isinstance(linear, (list, np.ndarray, QuantumCircuit)): - raise CircuitError( - "A linear function must be represented either by a list, " - "a numpy array, or a quantum circuit with linear gates." - ) - if isinstance(linear, QuantumCircuit): - # The following function will raise a CircuitError if there are nonlinear gates. - original_circuit = linear - linear = _linear_quantum_circuit_to_mat(linear) + # pylint: disable=cyclic-import + from qiskit.circuit.library import PermutationGate - else: + if isinstance(linear, (list, np.ndarray)): original_circuit = None # Normalize to numpy array (coercing entries to 0s and 1s) @@ -116,10 +110,93 @@ def __init__( "A linear function must be represented by an invertible matrix." ) + elif isinstance(linear, QuantumCircuit): + # The following function will raise a CircuitError if there are nonlinear gates. + original_circuit = linear + linear = LinearFunction._circuit_to_mat(linear) + + elif isinstance(linear, LinearFunction): + pass + + elif isinstance(linear, PermutationGate): + pass + + elif isinstance(linear, Clifford): + pass + + else: + raise CircuitError( + "A linear function must be represented either by a list, " + "a numpy array, or a quantum circuit with linear gates." + ) + super().__init__( name="linear_function", num_qubits=len(linear), params=[linear, original_circuit] ) + @staticmethod + def _circuit_to_mat(qc: QuantumCircuit): + """This creates a nxn matrix corresponding to the given quantum circuit.""" + nq = qc.num_qubits + mat = np.eye(nq, nq, dtype=bool) + + for instruction in qc.data: + if instruction.operation.name in ("barrier", "delay"): + # can be ignored + continue + elif instruction.operation.name == "cx": + # implemented directly + cb = qc.find_bit(instruction.qubits[0]).index + tb = qc.find_bit(instruction.qubits[1]).index + mat[tb, :] = (mat[tb, :]) ^ (mat[cb, :]) + continue + elif instruction.operation.name == "swap": + # implemented directly + cb = qc.find_bit(instruction.qubits[0]).index + tb = qc.find_bit(instruction.qubits[1]).index + mat[[cb, tb]] = mat[[tb, cb]] + continue + + # In all other cases, we construct the linear function for the operation. + # and compose (multiply) linear matrices. + + if getattr(instruction.operation, "definition", None) is not None: + other = LinearFunction(instruction.operation.definition) + else: + other = LinearFunction(instruction.operation) + + positions = [qc.find_bit(q).index for q in instruction.qubits] + other = other.extend_with_identity(len(mat), positions) + mat = np.dot(other.linear.astype(int), mat.astype(int)) % 2 + mat = mat.astype(bool) + + return mat + + @staticmethod + def _clifford_to_mat(cliff): + """This creates a nxn matrix corresponding to the given Clifford, when Clifford + can be converted to a linear function. This is possible when the clifford has + tableau of the form [[A, B], [C, D]], with B = C = 0 and D = A^{-1}^t, and zero + phase vector. In this case, the required matrix is A^t. + Raises an error otherwise. + """ + num_qubits = cliff.num_qubits + + if ( + cliff.phase.any() + or cliff.destab_z.any() + or cliff.stab_x.any() + or not np.array_equal( + np.dot(np.transpose(cliff.destab_x.astype(int)), cliff.stab_z.astype(int)) % 2, + np.eye(num_qubits), + ) + ): + raise CircuitError( + "The given clifford does not correspond to a linear function." + ) + + return np.transpose(cliff.destab_x) + def __eq__(self, other): """Check if two linear functions represent the same matrix.""" return (self.linear == other.linear).all() @@ -194,33 +271,3 @@ def extend_with_identity(self, nq: int, positions: list[int]) -> LinearFunction: extended_mat[positions, pos] = self.linear[:, i] return LinearFunction(extended_mat) - - -def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): - """This creates a n x n matrix corresponding to the given linear quantum circuit.""" - nq = qc.num_qubits - mat = np.eye(nq, nq, dtype=bool) - - for instruction in qc.data: - if instruction.operation.name == "cx": - cb = qc.find_bit(instruction.qubits[0]).index - tb = qc.find_bit(instruction.qubits[1]).index - mat[tb, :] = (mat[tb, :]) ^ (mat[cb, :]) - elif instruction.operation.name == "swap": - cb = qc.find_bit(instruction.qubits[0]).index - tb = qc.find_bit(instruction.qubits[1]).index - mat[[cb, tb]] = mat[[tb, cb]] - elif instruction.operation.name in ("barrier", "delay"): - continue - elif getattr(instruction.qoperation, "definition", None) is not None: - # Iteratively construct linear function for the operation, and - # compose (multiply) linear matrices. - other = LinearFunction(instruction.operation.definition) - positions = [qc.find_bit(q).index for q in instruction.qubits] - other = other.extend_with_identity(len(mat), positions) - mat = np.dot(other.linear.astype(int), mat.astype(int)) % 2 - mat = mat.astype(bool) - else: - raise CircuitError("A linear quantum circuit can include only CX and SWAP gates.") - - return mat From 8f3c904cfc2b55788f01593ec0e3ce034e07d5d4 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 12:44:46 +0300 Subject: [PATCH 05/18] tests for linear functions from cliffords --- .../generalized_gates/linear_function.py | 8 +++-- .../circuit/library/test_linear_function.py | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 9301f2029754..92bce2c9955d 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -88,9 +88,9 @@ def __init__( # pylint: disable=cyclic-import from qiskit.circuit.library import PermutationGate - if isinstance(linear, (list, np.ndarray)): - original_circuit = None + original_circuit = None + if isinstance(linear, (list, np.ndarray)): # Normalize to numpy array (coercing entries to 0s and 1s) try: linear = np.array(linear, dtype=bool, copy=True) @@ -122,7 +122,9 @@ def __init__( pass elif isinstance(linear, Clifford): - pass + # The following function will raise a CircuitError if clifford does not correspond + # to a linear function. + linear = LinearFunction._clifford_to_mat(linear) else: raise CircuitError( diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index 2bdee2a88b88..71c584ce1a11 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -16,6 +16,7 @@ import numpy as np from ddt import ddt, data +from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase from qiskit.circuit import QuantumCircuit @@ -328,6 +329,40 @@ def test_from_nested_quantum_circuit(self): expected = LinearFunction([[0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0]]) self.assertEquals(linear_function, expected) + def test_from_clifford_when_possible(self): + """Test constructing a linear function from a clifford which corresponds to a valid + linear function.""" + + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.swap(1, 2) + + # Linear function constructed from qc + linear_from_qc = LinearFunction(qc) + + # Linear function constructed from clifford + cliff = Clifford(qc) + linear_from_clifford = LinearFunction(cliff) + + self.assertEquals(linear_from_qc, linear_from_clifford) + + def test_to_clifford_and_back(self): + """Test converting linear function to clifford and back.""" + linear = LinearFunction([[1, 1, 1], [0, 1, 1], [0, 0, 1]]) + cliff = Clifford(linear) + linear_from_clifford = LinearFunction(cliff) + self.assertEquals(linear, linear_from_clifford) + + def test_from_clifford_when_impossible(self): + """Test that constructing a linear function from a clifford that does not correspond + to a linear function produces a circuit error.""" + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.h(0) + qc.swap(1, 2) + with self.assertRaises(CircuitError): + LinearFunction(qc) + if __name__ == "__main__": unittest.main() From 52dfa8e50c943cd4370423fa765dfd22a0b40dd6 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 14:03:52 +0300 Subject: [PATCH 06/18] linear function from permutation gate --- .../library/generalized_gates/linear_function.py | 11 ++++++++++- test/python/circuit/library/test_linear_function.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 92bce2c9955d..8f6733558314 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -119,7 +119,7 @@ def __init__( pass elif isinstance(linear, PermutationGate): - pass + linear = LinearFunction._permutation_to_mat(linear) elif isinstance(linear, Clifford): # The following function will raise a CircuitError if clifford does not correspond @@ -199,6 +199,15 @@ def _clifford_to_mat(cliff): return np.transpose(cliff.destab_x) + @staticmethod + def _permutation_to_mat(perm): + """This creates a nxn matrix from a given permutation gate.""" + nq = len(perm.pattern) + mat = np.zeros((nq, nq), dtype=bool) + for i, j in enumerate(perm.pattern): + mat[i, j] = True + return mat + def __eq__(self, other): """Check if two linear functions represent the same matrix.""" return (self.linear == other.linear).all() diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index 71c584ce1a11..eb47eb0e3357 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -16,6 +16,7 @@ import numpy as np from ddt import ddt, data +from qiskit.circuit.library import PermutationGate from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase from qiskit.circuit import QuantumCircuit @@ -363,6 +364,15 @@ def test_from_clifford_when_impossible(self): with self.assertRaises(CircuitError): LinearFunction(qc) + def test_from_permutation_gate(self): + """Test constructing a linear function from a permutation gate.""" + pattern = [1, 2, 0, 3] + perm_gate = PermutationGate(pattern) + linear_from_perm = LinearFunction(perm_gate) + self.assertTrue(linear_from_perm.is_permutation()) + extracted_pattern = linear_from_perm.permutation_pattern() + self.assertTrue(np.all(pattern == extracted_pattern)) + if __name__ == "__main__": unittest.main() From 688b1bc246417e2a8eda94e34e9571a39774ddfd Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 14:49:04 +0300 Subject: [PATCH 07/18] simplifying condition for checking whether clifford is a linear function --- .../library/generalized_gates/linear_function.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 8f6733558314..5062808efd87 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -127,6 +127,7 @@ def __init__( linear = LinearFunction._clifford_to_mat(linear) else: + # TODO: CHANGE THIS ERROR MESSAGE!! raise CircuitError( "A linear function must be represented either by a list, " "a numpy array, or a quantum circuit with linear gates." @@ -182,21 +183,12 @@ def _clifford_to_mat(cliff): phase vector. In this case, the required matrix is A^t. Raises an error otherwise. """ - num_qubits = cliff.num_qubits - - if ( - cliff.phase.any() - or cliff.destab_z.any() - or cliff.stab_x.any() - or not np.array_equal( - np.dot(np.transpose(cliff.destab_x.astype(int)), cliff.stab_z.astype(int)) % 2, - np.eye(num_qubits), - ) - ): + # Note: since cliff is a valid Clifford, then the condition D = A^{-1}^t + # holds automatically once B = C = 0. + if cliff.phase.any() or cliff.destab_z.any() or cliff.stab_x.any(): raise CircuitError( "The given clifford does not correspond to a linear function." ) - return np.transpose(cliff.destab_x) @staticmethod From d18719de2847d5c25b80a9617938f112249e23f0 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 15:10:00 +0300 Subject: [PATCH 08/18] linear function from another --- .../generalized_gates/linear_function.py | 12 ++++---- .../circuit/library/test_linear_function.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 5062808efd87..1eed213bce07 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -116,7 +116,7 @@ def __init__( linear = LinearFunction._circuit_to_mat(linear) elif isinstance(linear, LinearFunction): - pass + linear = linear.linear.copy() elif isinstance(linear, PermutationGate): linear = LinearFunction._permutation_to_mat(linear) @@ -126,12 +126,12 @@ def __init__( # to a linear function. linear = LinearFunction._clifford_to_mat(linear) + # Note: if we wanted, we could also try to construct a linear function from a + # general operator, by first attempting to convert it to clifford, and then to + # a linear function. + else: - # TODO: CHANGE THIS ERROR MESSAGE!! - raise CircuitError( - "A linear function must be represented either by a list, " - "a numpy array, or a quantum circuit with linear gates." - ) + raise CircuitError("A linear function cannot be successfully constructed.") super().__init__( name="linear_function", num_qubits=len(linear), params=[linear, original_circuit] diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index eb47eb0e3357..a7e31da53b1e 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -373,6 +373,34 @@ def test_from_permutation_gate(self): extracted_pattern = linear_from_perm.permutation_pattern() self.assertTrue(np.all(pattern == extracted_pattern)) + def test_from_linear_function(self): + """Test constructing a linear function from another linear function.""" + linear_function1 = LinearFunction([[1, 1, 1], [0, 1, 1], [0, 0, 1]]) + linear_function2 = LinearFunction(linear_function1) + self.assertEquals(linear_function1, linear_function2) + + def test_from_quantum_circuit_with_linear_functions(self): + """Test constructing a linear function from a quantum circuit with + linear functions.""" + + qc1 = QuantumCircuit(3) + qc1.swap(1, 2) + linear1 = LinearFunction(qc1) + print(linear1) + + qc2 = QuantumCircuit(2) + qc2.swap(0, 1) + linear2 = LinearFunction(qc2) + + qc3 = QuantumCircuit(4) + qc3.append(linear1, [0, 1, 2]) + qc3.append(linear2, [2, 3]) + linear3 = LinearFunction(qc3) + # linear3 is a permutation: 1->3->2, 0 unchanged + + expected = LinearFunction([[1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 1, 0, 0]]) + self.assertEquals(linear3, expected) + if __name__ == "__main__": unittest.main() From be7909340686248dc9a6ceed8ed5ca9e956bd7b7 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 15:40:35 +0300 Subject: [PATCH 09/18] pass over release notes --- ...r-functions-usability-45265f293a80a6e5.yaml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml index 9eff00af0e50..f4aacdd2b520 100644 --- a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml +++ b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml @@ -1,13 +1,23 @@ --- features: + - | + Allowing to construct a :class:`.LinearFunction` object from more general quantum circuits, + that may contain: + + * Barriers (of type :class:`~qiskit.circuit.Barrier`) and delays (:class:`~qiskit.circuit.Delay`), + which are simply ignored + * Permutations (of type :class:`~qiskit.circuit.library.PermutationGate`) + * Other linear functions + * Cliffords (of type :class:`.Clifford`), when the Clifford represents a linear function + (and a ``CircuitError`` exception is raised if not) + * Nested quantum circuits of this form + - | Added :meth:`.LinearFunction.__eq__` method. Two objects of type :class:`.LinearFunction` are considered equal when their representations as binary invertible matrices are equal. - | Added :meth:`.LinearFunction.extend_with_identity` method, which allows to extend a linear function over ``k`` qubits to a linear function over ``n >= k`` qubits, - with identities on other subsystems. - - | - Allowing to construct a :class:`.LinearFunction` from a quantum circuit with - :class:`~qiskit.circuit.Barrier` and :class:`~qiskit.circuit.Delay` instructions. + specifying the new positions of the original qubits and padding with identities on the + remaining qubits. From 0a2a7ff492506ba942f3b486eb1f0986e70ecce7 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 1 May 2023 15:41:03 +0300 Subject: [PATCH 10/18] black --- qiskit/circuit/library/generalized_gates/linear_function.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 1eed213bce07..637ca4160e32 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -186,9 +186,7 @@ def _clifford_to_mat(cliff): # Note: since cliff is a valid Clifford, then the condition D = A^{-1}^t # holds automatically once B = C = 0. if cliff.phase.any() or cliff.destab_z.any() or cliff.stab_x.any(): - raise CircuitError( - "The given clifford does not correspond to a linear function." - ) + raise CircuitError("The given clifford does not correspond to a linear function.") return np.transpose(cliff.destab_x) @staticmethod From 23478d4a63c688ff0ce70b1e5b41e28fd03cc9ac Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 2 May 2023 12:09:27 +0300 Subject: [PATCH 11/18] pylint fixes --- .../library/generalized_gates/linear_function.py | 12 ++++++------ test/python/circuit/library/test_linear_function.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 637ca4160e32..67cc902dde99 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -16,7 +16,6 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError -from qiskit.quantum_info import Clifford from qiskit.synthesis.linear import check_invertible_binary_matrix @@ -87,6 +86,7 @@ def __init__( # pylint: disable=cyclic-import from qiskit.circuit.library import PermutationGate + from qiskit.quantum_info import Clifford original_circuit = None @@ -147,13 +147,13 @@ def _circuit_to_mat(qc: QuantumCircuit): if instruction.operation.name in ("barrier", "delay"): # can be ignored continue - elif instruction.operation.name == "cx": + if instruction.operation.name == "cx": # implemented directly cb = qc.find_bit(instruction.qubits[0]).index tb = qc.find_bit(instruction.qubits[1]).index mat[tb, :] = (mat[tb, :]) ^ (mat[cb, :]) continue - elif instruction.operation.name == "swap": + if instruction.operation.name == "swap": # implemented directly cb = qc.find_bit(instruction.qubits[0]).index tb = qc.find_bit(instruction.qubits[1]).index @@ -253,12 +253,12 @@ def permutation_pattern(self): locs = np.where(linear == 1) return locs[1] - def extend_with_identity(self, nq: int, positions: list[int]) -> LinearFunction: + def extend_with_identity(self, num_qubits: int, positions: list[int]) -> LinearFunction: """Extend linear function to a linear function over nq qubits, with identities on other subsystems. Args: - nq: number of qubits of the extended function. + num_qubits: number of qubits of the extended function. positions: describes the positions of original qubits in the extended function's qubits. @@ -266,7 +266,7 @@ def extend_with_identity(self, nq: int, positions: list[int]) -> LinearFunction: Returns: LinearFunction: extended linear function. """ - extended_mat = np.eye(nq, dtype=bool) + extended_mat = np.eye(num_qubits, dtype=bool) for i, pos in enumerate(positions): extended_mat[positions, pos] = self.linear[:, i] diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index a7e31da53b1e..4666d21ccb3e 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -328,7 +328,7 @@ def test_from_nested_quantum_circuit(self): linear_function = LinearFunction(qc3) expected = LinearFunction([[0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0]]) - self.assertEquals(linear_function, expected) + self.assertEqual(linear_function, expected) def test_from_clifford_when_possible(self): """Test constructing a linear function from a clifford which corresponds to a valid @@ -345,14 +345,14 @@ def test_from_clifford_when_possible(self): cliff = Clifford(qc) linear_from_clifford = LinearFunction(cliff) - self.assertEquals(linear_from_qc, linear_from_clifford) + self.assertEqual(linear_from_qc, linear_from_clifford) def test_to_clifford_and_back(self): """Test converting linear function to clifford and back.""" linear = LinearFunction([[1, 1, 1], [0, 1, 1], [0, 0, 1]]) cliff = Clifford(linear) linear_from_clifford = LinearFunction(cliff) - self.assertEquals(linear, linear_from_clifford) + self.assertEqual(linear, linear_from_clifford) def test_from_clifford_when_impossible(self): """Test that constructing a linear function from a clifford that does not correspond @@ -377,7 +377,7 @@ def test_from_linear_function(self): """Test constructing a linear function from another linear function.""" linear_function1 = LinearFunction([[1, 1, 1], [0, 1, 1], [0, 0, 1]]) linear_function2 = LinearFunction(linear_function1) - self.assertEquals(linear_function1, linear_function2) + self.assertEqual(linear_function1, linear_function2) def test_from_quantum_circuit_with_linear_functions(self): """Test constructing a linear function from a quantum circuit with @@ -399,7 +399,7 @@ def test_from_quantum_circuit_with_linear_functions(self): # linear3 is a permutation: 1->3->2, 0 unchanged expected = LinearFunction([[1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 1, 0, 0]]) - self.assertEquals(linear3, expected) + self.assertEqual(linear3, expected) if __name__ == "__main__": From a40562707f981de67a5f5565f4d4b9507b646baa Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 2 May 2023 13:57:31 +0300 Subject: [PATCH 12/18] update docstring --- .../generalized_gates/linear_function.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 67cc902dde99..9b00c0df9790 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -61,18 +61,18 @@ class LinearFunction(Gate): `Online at umich.edu. `_ """ - def __init__( - self, - linear: list[list[int]] | np.ndarray | QuantumCircuit, - validate_input: bool | None = False, - ) -> None: + def __init__(self, linear, validate_input=False): """Create a new linear function. Args: - linear (list[list] or ndarray[bool] or QuantumCircuit): - either an n x n matrix, describing the linear function, - or a quantum circuit composed of linear gates only - (currently supported gates are CX and SWAP). + linear (list[list] or ndarray[bool] or QuantumCircuit or LinearFunction + or PermutationGate or Clifford): data from which a linear function + can be constructed. It can be either a nxn matrix (describing the + linear transformation), a permutation (which is a special case of + a linear function), another linear function, a clifford (when it + corresponds to a linear function), or a quantum circuit composed of + linear gates (CX and SWAP) and other objects described above, including + nested subcircuits. validate_input: if True, performs more expensive input validation checks, such as checking that a given n x n matrix is invertible. From 752cd9b657e3e4ee90af3b452f7800c94db54401 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 2 May 2023 14:00:57 +0300 Subject: [PATCH 13/18] more docstring fixes --- .../circuit/library/generalized_gates/linear_function.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 9b00c0df9790..62c4a3f40aa1 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -79,9 +79,10 @@ def __init__(self, linear, validate_input=False): Raises: CircuitError: if the input is invalid: - either a matrix is non {square, invertible}, - or a quantum circuit contains non-linear gates. - + either the input matrix is not square or not invertible, + or the input quantum circuit contains non-linear objects + (for example, a Hadamard gate, or a Clifford that does + not correspond to a linear function). """ # pylint: disable=cyclic-import From 6288cd80eef843abed32a24a35c42e7f0044dc98 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 16 May 2023 11:06:33 +0300 Subject: [PATCH 14/18] adding pretty-printing functions for linear functions --- .../generalized_gates/linear_function.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 62c4a3f40aa1..5636def1f5e9 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -223,7 +223,7 @@ def synthesize(self): @property def linear(self): - """Returns the n x n matrix representing this linear function""" + """Returns the n x n matrix representing this linear function.""" return self.params[0] @property @@ -273,3 +273,28 @@ def extend_with_identity(self, num_qubits: int, positions: list[int]) -> LinearF extended_mat[positions, pos] = self.linear[:, i] return LinearFunction(extended_mat) + + def mat_str(self): + """Return string representation of the linear function + viewed as a matrix with 0/1 entries. + """ + return str(self.linear.astype(int)) + + def function_str(self): + """Return string representation of the linear function + viewed as a linear transformation. + """ + out = "(" + mat = self.linear + for row in range(self.num_qubits): + first_entry = True + for col in range(self.num_qubits): + if mat[row, col]: + if not first_entry: + out += " + " + out += "x_" + str(col) + first_entry = False + if row != self.num_qubits - 1: + out += ", " + out += ")\n" + return out From 68c2aa93c31f570115994d93b46107a9dd0a3ed6 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 11 Jul 2023 10:52:13 +0300 Subject: [PATCH 15/18] adding pretty-printing functions to the release notes --- .../notes/linear-functions-usability-45265f293a80a6e5.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml index f4aacdd2b520..ffffff32f169 100644 --- a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml +++ b/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml @@ -20,4 +20,9 @@ features: a linear function over ``k`` qubits to a linear function over ``n >= k`` qubits, specifying the new positions of the original qubits and padding with identities on the remaining qubits. - + - | + Added two methods for pretty-printing :class:`.LinearFunction` objects: + :meth:`.LinearFunction.mat_str`, which returns the string representation of the linear + function viewed as a matrix with 0/1 entries, and + :meth:`.LinearFunction.function_str`, which returns the string representation of the + linear function viewed as a linear transformation. From 61907787e364270d0431d1455cefe14220cabd64 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 11 Jul 2023 13:43:56 +0300 Subject: [PATCH 16/18] adding pseudo-random tests --- .../circuit/library/test_linear_function.py | 132 ++++++++++++++++-- 1 file changed, 117 insertions(+), 15 deletions(-) diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index 4666d21ccb3e..4209d1a4e4a0 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -16,40 +16,119 @@ import numpy as np from ddt import ddt, data -from qiskit.circuit.library import PermutationGate -from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase from qiskit.circuit import QuantumCircuit +from qiskit.quantum_info import Clifford from qiskit.circuit.library.standard_gates import CXGate, SwapGate -from qiskit.circuit.library.generalized_gates import LinearFunction +from qiskit.circuit.library.generalized_gates import LinearFunction, PermutationGate from qiskit.circuit.exceptions import CircuitError from qiskit.synthesis.linear import random_invertible_binary_matrix from qiskit.quantum_info.operators import Operator -def random_linear_circuit(num_qubits, num_gates, seed=None): +def random_linear_circuit( + num_qubits, + num_gates, + seed=None, + barrier=False, + delay=False, + permutation=False, + linear=False, + clifford=False, + recursion_depth=0, +): + """Generate a pseudo random linear circuit.""" - instructions = { - "cx": (CXGate(), 2), - "swap": (SwapGate(), 2), - } + if num_qubits == 0: + raise CircuitError("Cannot construct a random linear circuit with 0 qubits.") + + circ = QuantumCircuit(num_qubits) + + instructions = ["cx", "swap"] if num_qubits >= 2 else [] + if barrier: + instructions.append("barrier") + if delay: + instructions.append("delay") + if permutation: + instructions.append("permutation") + if linear: + instructions.append("linear") + if clifford: + instructions.append("clifford") + if recursion_depth > 0: + instructions.append("nested") + + if not instructions: + # Return the empty circuit if there are no instructions to choose from. + return circ if isinstance(seed, np.random.Generator): rng = seed else: rng = np.random.default_rng(seed) - name_samples = rng.choice(tuple(instructions), num_gates) - - circ = QuantumCircuit(num_qubits) + name_samples = rng.choice(instructions, num_gates) for name in name_samples: - gate, nqargs = instructions[name] - qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() - circ.append(gate, qargs) + if name == "cx": + qargs = rng.choice(range(num_qubits), 2, replace=False).tolist() + circ.cx(*qargs) + elif name == "swap": + qargs = rng.choice(range(num_qubits), 2, replace=False).tolist() + circ.swap(*qargs) + elif name == "barrier": + nqargs = rng.choice(range(1, num_qubits + 1)) + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + circ.barrier(qargs) + elif name == "delay": + qarg = rng.choice(range(num_qubits)) + circ.delay(100, qarg) + elif name == "linear": + nqargs = rng.choice(range(1, num_qubits + 1)) + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + mat = random_invertible_binary_matrix(nqargs, seed=rng) + circ.append(LinearFunction(mat), qargs) + elif name == "permutation": + nqargs = rng.choice(range(1, num_qubits + 1)) + pattern = list(np.random.permutation(range(nqargs))) + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + circ.append(PermutationGate(pattern), qargs) + elif name == "clifford": + # In order to construct a Clifford that corresponds to a linear function, + # we construct a small random linear circuit, and convert it to Clifford. + nqargs = rng.choice(range(1, num_qubits + 1)) + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + subcirc = random_linear_circuit( + nqargs, + num_gates=5, + seed=rng, + barrier=False, + delay=False, + permutation=False, + linear=False, + clifford=False, + recursion_depth=0, + ) + cliff = Clifford(subcirc) + circ.append(cliff, qargs) + elif name == "nested": + nqargs = rng.choice(range(1, num_qubits + 1)) + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + subcirc = random_linear_circuit( + nqargs, + num_gates=5, + seed=rng, + barrier=False, + delay=False, + permutation=False, + linear=False, + clifford=False, + recursion_depth=recursion_depth - 1, + ) + circ.append(subcirc, qargs) return circ @@ -386,7 +465,6 @@ def test_from_quantum_circuit_with_linear_functions(self): qc1 = QuantumCircuit(3) qc1.swap(1, 2) linear1 = LinearFunction(qc1) - print(linear1) qc2 = QuantumCircuit(2) qc2.swap(0, 1) @@ -401,6 +479,30 @@ def test_from_quantum_circuit_with_linear_functions(self): expected = LinearFunction([[1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 1, 0, 0]]) self.assertEqual(linear3, expected) + @data(2, 3, 4, 5, 6, 7, 8) + def test_clifford_linear_function_equivalence(self, num_qubits): + """Pseudo-random tests for constructing a random linear circuit, + converting this circuit both to a linear function and to a clifford, + and checking that the two are equivalent as Cliffords and as LinearFunctions. + """ + + # Note: Cliffords cannot be yet constructed from PermutationGate objects + qc = random_linear_circuit( + num_qubits, + 100, + seed=0, + barrier=True, + delay=True, + permutation=False, + linear=True, + clifford=True, + recursion_depth=2, + ) + qc_to_linear_function = LinearFunction(qc) + qc_to_clifford = Clifford(qc) + self.assertEqual(Clifford(qc_to_linear_function), qc_to_clifford) + self.assertEqual(qc_to_linear_function, LinearFunction(qc_to_clifford)) + if __name__ == "__main__": unittest.main() From 5a1cf9fd81e56b51219e0cd68a06c65341844274 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 15 Jul 2023 10:18:24 +0300 Subject: [PATCH 17/18] moving import to the top of the file --- qiskit/circuit/library/generalized_gates/linear_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 5636def1f5e9..c7267a553a90 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -17,6 +17,7 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError from qiskit.synthesis.linear import check_invertible_binary_matrix +from qiskit.circuit.library.generalized_gates.permutation import PermutationGate class LinearFunction(Gate): @@ -86,7 +87,6 @@ def __init__(self, linear, validate_input=False): """ # pylint: disable=cyclic-import - from qiskit.circuit.library import PermutationGate from qiskit.quantum_info import Clifford original_circuit = None From ad347ed08330582d94b9d32dc51a4df848a82777 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 15 Jul 2023 10:24:09 +0300 Subject: [PATCH 18/18] Fix to __eq__ as suggested in review --- qiskit/circuit/library/generalized_gates/linear_function.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index c7267a553a90..e6697726c028 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -201,6 +201,8 @@ def _permutation_to_mat(perm): def __eq__(self, other): """Check if two linear functions represent the same matrix.""" + if not isinstance(other, LinearFunction): + return False return (self.linear == other.linear).all() def validate_parameter(self, parameter):