diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 73588a421ac7..4a763856c82d 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -211,9 +211,15 @@ :template: autosummary/class_no_inherited_members.rst AND + AndGate OR + OrGate XOR + BitwiseXorGate InnerProduct + InnerProductGate + +.. autofunction:: random_bitwise_xor Basis Change Circuits ===================== @@ -523,9 +529,14 @@ from .hamiltonian_gate import HamiltonianGate from .boolean_logic import ( AND, + AndGate, OR, + OrGate, XOR, + BitwiseXorGate, + random_bitwise_xor, InnerProduct, + InnerProductGate, ) from .basis_change import QFT, QFTGate from .arithmetic import ( diff --git a/qiskit/circuit/library/boolean_logic/__init__.py b/qiskit/circuit/library/boolean_logic/__init__.py index b273f0d6a245..736f983b05f9 100644 --- a/qiskit/circuit/library/boolean_logic/__init__.py +++ b/qiskit/circuit/library/boolean_logic/__init__.py @@ -12,7 +12,7 @@ """The Boolean logic circuit library.""" -from .quantum_and import AND -from .quantum_or import OR -from .quantum_xor import XOR -from .inner_product import InnerProduct +from .quantum_and import AND, AndGate +from .quantum_or import OR, OrGate +from .quantum_xor import XOR, BitwiseXorGate, random_bitwise_xor +from .inner_product import InnerProduct, InnerProductGate diff --git a/qiskit/circuit/library/boolean_logic/inner_product.py b/qiskit/circuit/library/boolean_logic/inner_product.py index dcaced069119..84b3807fb56c 100644 --- a/qiskit/circuit/library/boolean_logic/inner_product.py +++ b/qiskit/circuit/library/boolean_logic/inner_product.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 2024. # # 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 @@ -11,10 +11,11 @@ # that they have been altered from the originals. -"""InnerProduct circuit.""" +"""InnerProduct circuit and gate.""" -from qiskit.circuit import QuantumRegister, QuantumCircuit +from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate +from qiskit.utils.deprecation import deprecate_func class InnerProduct(QuantumCircuit): @@ -61,6 +62,11 @@ class InnerProduct(QuantumCircuit): _generate_circuit_library_visualization(circuit) """ + @deprecate_func( + since="1.3", + additional_msg="Use qiskit.circuit.library.InnerProductGate instead.", + pending=True, + ) def __init__(self, num_qubits: int) -> None: """Return a circuit to compute the inner product of 2 n-qubit registers. @@ -76,3 +82,74 @@ def __init__(self, num_qubits: int) -> None: super().__init__(*inner.qregs, name="inner_product") self.compose(inner.to_gate(), qubits=self.qubits, inplace=True) + + +class InnerProductGate(Gate): + r"""A 2n-qubit Boolean function that computes the inner product of + two n-qubit vectors over :math:`F_2`. + + This implementation is a phase oracle which computes the following transform. + + .. math:: + + \mathcal{IP}_{2n} : F_2^{2n} \rightarrow {-1, 1} + \mathcal{IP}_{2n}(x_1, \cdots, x_n, y_1, \cdots, y_n) = (-1)^{x.y} + + The corresponding unitary is a diagonal, which induces a -1 phase on any inputs + where the inner product of the top and bottom registers is 1. Otherwise, it keeps + the input intact. + + .. parsed-literal:: + + + q0_0: ─■────────── + │ + q0_1: ─┼──■─────── + │ │ + q0_2: ─┼──┼──■──── + │ │ │ + q0_3: ─┼──┼──┼──■─ + │ │ │ │ + q1_0: ─■──┼──┼──┼─ + │ │ │ + q1_1: ────■──┼──┼─ + │ │ + q1_2: ───────■──┼─ + │ + q1_3: ──────────■─ + + + Reference Circuit: + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import InnerProductGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(8) + circuit.append(InnerProductGate(4), [0, 1, 2, 3, 4, 5, 6, 7]) + _generate_circuit_library_visualization(circuit) + """ + + def __init__( + self, + num_qubits: int, + ) -> None: + """ + Args: + num_qubits: width of top and bottom registers (half total number of qubits). + """ + super().__init__("inner_product", 2 * num_qubits, []) + + def _define(self): + num_qubits = self.num_qubits // 2 + qr_a = QuantumRegister(num_qubits, name="x") + qr_b = QuantumRegister(num_qubits, name="y") + + circuit = QuantumCircuit(qr_a, qr_b, name="inner_product") + for i in range(num_qubits): + circuit.cz(qr_a[i], qr_b[i]) + + self.definition = circuit + + def __eq__(self, other): + return isinstance(other, InnerProductGate) and self.num_qubits == other.num_qubits diff --git a/qiskit/circuit/library/boolean_logic/quantum_and.py b/qiskit/circuit/library/boolean_logic/quantum_and.py index ce2d253ca85a..53099aa7be84 100644 --- a/qiskit/circuit/library/boolean_logic/quantum_and.py +++ b/qiskit/circuit/library/boolean_logic/quantum_and.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 2024. # # 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 @@ -11,11 +11,13 @@ # that they have been altered from the originals. -"""Implementations of boolean logic quantum circuits.""" +"""Boolean AND circuit and gate.""" + from __future__ import annotations -from qiskit.circuit import QuantumRegister, QuantumCircuit, AncillaRegister +from qiskit.circuit import QuantumRegister, QuantumCircuit, AncillaRegister, Gate from qiskit.circuit.library.standard_gates import MCXGate +from qiskit.utils.deprecation import deprecate_func class AND(QuantumCircuit): @@ -49,6 +51,11 @@ class AND(QuantumCircuit): """ + @deprecate_func( + since="1.3", + additional_msg="Use qiskit.circuit.library.AndGate instead.", + pending=True, + ) def __init__( self, num_variable_qubits: int, @@ -58,7 +65,7 @@ def __init__( """Create a new logical AND circuit. Args: - num_variable_qubits: The qubits of which the OR is computed. The result will be written + num_variable_qubits: The qubits of which the AND is computed. The result will be written into an additional result qubit. flags: A list of +1/0/-1 marking negations or omissions of qubits. mcx_mode: The mode to be used to implement the multi-controlled X gate. @@ -95,3 +102,99 @@ def __init__( super().__init__(*circuit.qregs, name="and") self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True) + + +class AndGate(Gate): + r"""A gate representing the logical AND operation on a number of qubits. + + For the AND operation the state :math:`|1\rangle` is interpreted as ``True``. The result + qubit is flipped, if the state of all variable qubits is ``True``. In this format, the AND + operation equals a multi-controlled X gate, which is controlled on all variable qubits. + Using a list of flags however, qubits can be skipped or negated. Practically, the flags + allow to skip controls or to apply pre- and post-X gates to the negated qubits. + + The AndGate gate without special flags equals the multi-controlled-X gate: + + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import AndGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(6) + circuit.append(AndGate(5), [0, 1, 2, 3, 4, 5]) + _generate_circuit_library_visualization(circuit) + + Using flags we can negate qubits or skip them. For instance, if we have 5 qubits and want to + return ``True`` if the first qubit is ``False`` and the last two are ``True`` we use the flags + ``[-1, 0, 0, 1, 1]``. + + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import AndGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(6) + circuit.append(AndGate(5, flags=[-1, 0, 0, 1, 1]), [0, 1, 2, 3, 4, 5]) + _generate_circuit_library_visualization(circuit) + + """ + + def __init__( + self, + num_variable_qubits: int, + flags: list[int] | None = None, + ) -> None: + """ + Args: + num_variable_qubits: The qubits of which the AND is computed. The result will be written + into an additional result qubit. + flags: A list of +1/0/-1 marking negations or omissions of qubits. + """ + super().__init__("and", num_variable_qubits + 1, []) + self.num_variable_qubits = num_variable_qubits + self.flags = flags + + def _define(self): + # add registers + qr_variable = QuantumRegister(self.num_variable_qubits, name="variable") + qr_result = QuantumRegister(1, name="result") + + # determine the control qubits: all that have a nonzero flag + flags = self.flags or [1] * self.num_variable_qubits + control_qubits = [q for q, flag in zip(qr_variable, flags) if flag != 0] + + # determine the qubits that need to be flipped (if a flag is < 0) + flip_qubits = [q for q, flag in zip(qr_variable, flags) if flag < 0] + + # create the definition circuit + circuit = QuantumCircuit(qr_variable, qr_result, name="and") + + if len(flip_qubits) > 0: + circuit.x(flip_qubits) + circuit.mcx(control_qubits, qr_result[:]) + if len(flip_qubits) > 0: + circuit.x(flip_qubits) + + self.definition = circuit + + # pylint: disable=unused-argument + def inverse(self, annotated: bool = False): + r"""Return inverted AND gate (itself). + + Args: + annotated: when set to ``True``, this is typically used to return an + :class:`.AnnotatedOperation` with an inverse modifier set instead of a concrete + :class:`.Gate`. However, for this class this argument is ignored as this gate + is self-inverse. + + Returns: + AndGate: inverse gate (self-inverse). + """ + return AndGate(self.num_variable_qubits, self.flags) + + def __eq__(self, other): + return ( + isinstance(other, AndGate) + and self.num_variable_qubits == other.num_variable_qubits + and self.flags == other.flags + ) diff --git a/qiskit/circuit/library/boolean_logic/quantum_or.py b/qiskit/circuit/library/boolean_logic/quantum_or.py index 91d6c4fbdd52..95b346b34846 100644 --- a/qiskit/circuit/library/boolean_logic/quantum_or.py +++ b/qiskit/circuit/library/boolean_logic/quantum_or.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 2024. # # 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 @@ -11,12 +11,14 @@ # that they have been altered from the originals. -"""Implementations of boolean logic quantum circuits.""" +"""Boolean OR circuit and gate.""" + from __future__ import annotations from typing import List, Optional -from qiskit.circuit import QuantumRegister, QuantumCircuit, AncillaRegister +from qiskit.circuit import QuantumRegister, QuantumCircuit, AncillaRegister, Gate from qiskit.circuit.library.standard_gates import MCXGate +from qiskit.utils.deprecation import deprecate_func class OR(QuantumCircuit): @@ -50,6 +52,11 @@ class OR(QuantumCircuit): """ + @deprecate_func( + since="1.3", + additional_msg="Use qiskit.circuit.library.OrGate instead.", + pending=True, + ) def __init__( self, num_variable_qubits: int, @@ -96,3 +103,100 @@ def __init__( super().__init__(*circuit.qregs, name="or") self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True) + + +class OrGate(Gate): + r"""A gate representing the logical OR operation on a number of qubits. + + For the OR operation the state :math:`|1\rangle` is interpreted as ``True``. The result + qubit is flipped, if the state of any variable qubit is ``True``. The OR is implemented using + a multi-open-controlled X gate (i.e. flips if the state is :math:`|0\rangle`) and + applying an X gate on the result qubit. + Using a list of flags, qubits can be skipped or negated. + + The OrGate gate without special flags: + + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import OrGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(6) + circuit.append(OrGate(5), [0, 1, 2, 3, 4, 5]) + _generate_circuit_library_visualization(circuit) + + Using flags we can negate qubits or skip them. For instance, if we have 5 qubits and want to + return ``True`` if the first qubit is ``False`` or one of the last two are ``True`` we use the + flags ``[-1, 0, 0, 1, 1]``. + + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import OrGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(6) + circuit.append(OrGate(5, flags=[-1, 0, 0, 1, 1]), [0, 1, 2, 3, 4, 5]) + _generate_circuit_library_visualization(circuit) + + """ + + def __init__( + self, + num_variable_qubits: int, + flags: list[int] | None = None, + ) -> None: + """ + Args: + num_variable_qubits: The qubits of which the AND is computed. The result will be written + into an additional result qubit. + flags: A list of +1/0/-1 marking negations or omissions of qubits. + """ + super().__init__("and", num_variable_qubits + 1, []) + self.num_variable_qubits = num_variable_qubits + self.flags = flags + + def _define(self): + # add registers + qr_variable = QuantumRegister(self.num_variable_qubits, name="variable") + qr_result = QuantumRegister(1, name="result") + + # determine the control qubits: all that have a nonzero flag + flags = self.flags or [1] * self.num_variable_qubits + control_qubits = [q for q, flag in zip(qr_variable, flags) if flag != 0] + + # determine the qubits that need to be flipped (if a flag is > 0) + flip_qubits = [q for q, flag in zip(qr_variable, flags) if flag > 0] + + # create the definition circuit + circuit = QuantumCircuit(qr_variable, qr_result, name="or") + + circuit.x(qr_result) + if len(flip_qubits) > 0: + circuit.x(flip_qubits) + circuit.mcx(control_qubits, qr_result[:]) + if len(flip_qubits) > 0: + circuit.x(flip_qubits) + + self.definition = circuit + + # pylint: disable=unused-argument + def inverse(self, annotated: bool = False): + r"""Return inverted OR gate (itself). + + Args: + annotated: when set to ``True``, this is typically used to return an + :class:`.AnnotatedOperation` with an inverse modifier set instead of a concrete + :class:`.Gate`. However, for this class this argument is ignored as this gate + is self-inverse. + + Returns: + OrGate: inverse gate (self-inverse). + """ + return OrGate(self.num_variable_qubits, self.flags) + + def __eq__(self, other): + return ( + isinstance(other, OrGate) + and self.num_variable_qubits == other.num_variable_qubits + and self.flags == other.flags + ) diff --git a/qiskit/circuit/library/boolean_logic/quantum_xor.py b/qiskit/circuit/library/boolean_logic/quantum_xor.py index 5b230345137d..73a2178830bc 100644 --- a/qiskit/circuit/library/boolean_logic/quantum_xor.py +++ b/qiskit/circuit/library/boolean_logic/quantum_xor.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 2024. # # 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 @@ -11,13 +11,14 @@ # that they have been altered from the originals. -"""XOR circuit.""" +"""Bitwise XOR circuit and gate.""" from typing import Optional import numpy as np -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError +from qiskit.utils.deprecation import deprecate_func class XOR(QuantumCircuit): @@ -28,6 +29,12 @@ class XOR(QuantumCircuit): This circuit can also represent addition by ``amount`` over the finite field GF(2). """ + @deprecate_func( + since="1.3", + additional_msg="Instead, for xor-ing with a specified amount, use BitwiseXorGate," + "and for xor-ing with a random amount, use random_bitwise_xor.", + pending=True, + ) def __init__( self, num_qubits: int, @@ -69,3 +76,90 @@ def __init__( super().__init__(*circuit.qregs, name="xor") self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True) + + +class BitwiseXorGate(Gate): + """An n-qubit gate for bitwise xor-ing the input with some integer ``amount``. + + The ``amount`` is xor-ed in bitstring form with the input. + + This gate can also represent addition by ``amount`` over the finite field GF(2). + + Reference Circuit: + + .. plot:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import BitwiseXorGate + from qiskit.visualization.library import _generate_circuit_library_visualization + circuit = QuantumCircuit(5) + circuit.append(BitwiseXorGate(5, amount=12), [0, 1, 2, 3, 4]) + _generate_circuit_library_visualization(circuit) + + """ + + def __init__( + self, + num_qubits: int, + amount: int, + ) -> None: + """ + Args: + num_qubits: the width of circuit. + amount: the xor amount in decimal form. + + Raises: + CircuitError: if the xor bitstring exceeds available qubits. + """ + if len(bin(amount)[2:]) > num_qubits: + raise CircuitError("Bits in 'amount' exceed circuit width") + + super().__init__("xor", num_qubits, []) + self.amount = amount + + def _define(self): + circuit = QuantumCircuit(self.num_qubits, name="xor") + amount = self.amount + for i in range(self.num_qubits): + bit = amount & 1 + amount = amount >> 1 + if bit == 1: + circuit.x(i) + + self.definition = circuit + + def __eq__(self, other): + return ( + isinstance(other, BitwiseXorGate) + and self.num_qubits == other.num_qubits + and self.amount == other.amount + ) + + # pylint: disable=unused-argument + def inverse(self, annotated: bool = False): + r"""Return inverted BitwiseXorGate gate (itself). + + Args: + annotated: when set to ``True``, this is typically used to return an + :class:`.AnnotatedOperation` with an inverse modifier set instead of a concrete + :class:`.Gate`. However, for this class this argument is ignored as this gate + is self-inverse. + + Returns: + BitwiseXorGate: inverse gate (self-inverse). + """ + return BitwiseXorGate(self.num_qubits, self.amount) + + +def random_bitwise_xor(num_qubits: int, seed: int) -> BitwiseXorGate: + """ + Create a random BitwiseXorGate. + + Args: + num_qubits: the width of circuit. + seed: random seed in case a random xor is requested. + """ + + rng = np.random.default_rng(seed) + amount = rng.integers(0, 2**num_qubits) + return BitwiseXorGate(num_qubits, amount) diff --git a/releasenotes/notes/boolean-logic-gates-40add5cf0b20b5e9.yaml b/releasenotes/notes/boolean-logic-gates-40add5cf0b20b5e9.yaml new file mode 100644 index 000000000000..d1de403ab4f7 --- /dev/null +++ b/releasenotes/notes/boolean-logic-gates-40add5cf0b20b5e9.yaml @@ -0,0 +1,14 @@ +--- +features_circuits: + - | + Quantum circuits in :mod:`qiskit.circuit.library.boolean_logic` now have equivalent + representations as :class:`.Gate` objects: + + * :class:`~qiskit.circuit.library.AndGate`, + representing :class:`~qiskit.circuit.library.AND`, + * :class:`~qiskit.circuit.library.OrGate`, + representing :class:`~qiskit.circuit.library.OR`, + * :class:`~qiskit.circuit.library.BitwiseXorGate`, + representing :class:`~qiskit.circuit.library.XOR`, + * :class:`~qiskit.circuit.library.InnerProductGate`, + representing :class:`~qiskit.circuit.library.InnerProduct`. diff --git a/test/python/circuit/library/test_boolean_logic.py b/test/python/circuit/library/test_boolean_logic.py index cdadc9142a97..265f494f4c0c 100644 --- a/test/python/circuit/library/test_boolean_logic.py +++ b/test/python/circuit/library/test_boolean_logic.py @@ -16,9 +16,18 @@ from ddt import ddt, data, unpack import numpy as np -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import XOR, InnerProduct, AND, OR -from qiskit.quantum_info import Statevector +from qiskit.circuit import QuantumCircuit, Gate +from qiskit.circuit.library import ( + XOR, + InnerProduct, + AND, + OR, + AndGate, + OrGate, + BitwiseXorGate, + InnerProductGate, +) +from qiskit.quantum_info import Statevector, Operator from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -26,11 +35,15 @@ class TestBooleanLogicLibrary(QiskitTestCase): """Test library of boolean logic quantum circuits.""" - def assertBooleanFunctionIsCorrect(self, boolean_circuit, reference): - """Assert that ``boolean_circuit`` implements the reference boolean function correctly.""" - circuit = QuantumCircuit(boolean_circuit.num_qubits) - circuit.h(list(range(boolean_circuit.num_variable_qubits))) - circuit.append(boolean_circuit.to_instruction(), list(range(boolean_circuit.num_qubits))) + def assertBooleanFunctionIsCorrect(self, boolean_object, reference): + """Assert that ``boolean_object`` implements the reference boolean function correctly.""" + circuit = QuantumCircuit(boolean_object.num_qubits) + circuit.h(list(range(boolean_object.num_variable_qubits))) + + if isinstance(boolean_object, Gate): + circuit.append(boolean_object, list(range(boolean_object.num_qubits))) + else: + circuit.append(boolean_object.to_instruction(), list(range(boolean_object.num_qubits))) # compute the statevector of the circuit statevector = Statevector.from_label("0" * circuit.num_qubits) @@ -38,18 +51,18 @@ def assertBooleanFunctionIsCorrect(self, boolean_circuit, reference): # trace out ancillas probabilities = statevector.probabilities( - qargs=list(range(boolean_circuit.num_variable_qubits + 1)) + qargs=list(range(boolean_object.num_variable_qubits + 1)) ) # compute the expected outcome by computing the entries of the statevector that should # have a 1 / sqrt(2**n) factor expectations = np.zeros_like(probabilities) - for x in range(2**boolean_circuit.num_variable_qubits): - bits = np.array(list(bin(x)[2:].zfill(boolean_circuit.num_variable_qubits)), dtype=int) + for x in range(2**boolean_object.num_variable_qubits): + bits = np.array(list(bin(x)[2:].zfill(boolean_object.num_variable_qubits)), dtype=int) result = reference(bits[::-1]) - entry = int(str(int(result)) + bin(x)[2:].zfill(boolean_circuit.num_variable_qubits), 2) - expectations[entry] = 1 / 2**boolean_circuit.num_variable_qubits + entry = int(str(int(result)) + bin(x)[2:].zfill(boolean_object.num_variable_qubits), 2) + expectations[entry] = 1 / 2**boolean_object.num_variable_qubits np.testing.assert_array_almost_equal(probabilities, expectations) @@ -63,6 +76,39 @@ def test_xor(self): expected.x(2) self.assertEqual(circuit.decompose(), expected) + def test_xor_gate(self): + """Test XOR-gate.""" + xor_gate = BitwiseXorGate(num_qubits=3, amount=4) + expected = QuantumCircuit(3) + expected.x(2) + self.assertEqual(Operator(xor_gate), Operator(expected)) + + @data( + (5, 12), + (6, 21), + ) + @unpack + def test_xor_equivalence(self, num_qubits, amount): + """Test that XOR-circuit and BitwiseXorGate yield equal operators.""" + xor_gate = BitwiseXorGate(num_qubits, amount) + xor_circuit = XOR(num_qubits, amount) + self.assertEqual(Operator(xor_gate), Operator(xor_circuit)) + + def test_xor_eq(self): + """Test BitwiseXorGate's equality method.""" + xor1 = BitwiseXorGate(num_qubits=5, amount=10) + xor2 = BitwiseXorGate(num_qubits=5, amount=10) + xor3 = BitwiseXorGate(num_qubits=5, amount=11) + self.assertEqual(xor1, xor2) + self.assertNotEqual(xor1, xor3) + + def test_xor_inverse(self): + """Test correctness of the BitwiseXorGate's inverse.""" + xor_gate = BitwiseXorGate(num_qubits=5, amount=10) + xor_gate_inverse = xor_gate.inverse() + self.assertEqual(xor_gate, xor_gate_inverse) + self.assertEqual(Operator(xor_gate), Operator(xor_gate_inverse).adjoint()) + def test_inner_product(self): """Test inner product circuit. @@ -75,6 +121,22 @@ def test_inner_product(self): expected.cz(2, 5) self.assertEqual(circuit.decompose(), expected) + def test_inner_product_gate(self): + """Test inner product gate.""" + inner_product = InnerProductGate(num_qubits=3) + expected = QuantumCircuit(6) + expected.cz(0, 3) + expected.cz(1, 4) + expected.cz(2, 5) + self.assertEqual(Operator(inner_product), Operator(expected)) + + @data(4, 5, 6) + def test_inner_product_equivalence(self, num_qubits): + """Test that XOR-circuit and BitwiseXorGate yield equal operators.""" + inner_product_gate = InnerProductGate(num_qubits) + inner_product_circuit = InnerProduct(num_qubits) + self.assertEqual(Operator(inner_product_gate), Operator(inner_product_circuit)) + @data( (2, None, "noancilla"), (5, None, "noancilla"), @@ -100,6 +162,57 @@ def reference(bits): self.assertBooleanFunctionIsCorrect(or_circuit, reference) + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_or_gate(self, num_variables, flags): + """Test correctness of the OrGate.""" + or_gate = OrGate(num_variables, flags) + flags = flags or [1] * num_variables + + def reference(bits): + flagged = [] + for flag, bit in zip(flags, bits): + if flag < 0: + flagged += [1 - bit] + elif flag > 0: + flagged += [bit] + return np.any(flagged) + + self.assertBooleanFunctionIsCorrect(or_gate, reference) + + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_or_gate_inverse(self, num_variables, flags): + """Test correctness of the OrGate's inverse.""" + or_gate = OrGate(num_variables, flags) + or_gate_inverse = or_gate.inverse() + self.assertEqual(Operator(or_gate), Operator(or_gate_inverse).adjoint()) + + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_or_equivalence(self, num_variables, flags): + """Test that OR-circuit and OrGate yield equal operators + (when not using ancilla qubits). + """ + or_gate = OrGate(num_variables, flags) + or_circuit = OR(num_variables, flags) + self.assertEqual(Operator(or_gate), Operator(or_circuit)) + @data( (2, None, "noancilla"), (2, [-1, 1], "v-chain"), @@ -108,7 +221,7 @@ def reference(bits): ) @unpack def test_and(self, num_variables, flags, mcx_mode): - """Test the and circuit.""" + """Test the AND-circuit.""" and_circuit = AND(num_variables, flags, mcx_mode=mcx_mode) flags = flags or [1] * num_variables @@ -123,6 +236,57 @@ def reference(bits): self.assertBooleanFunctionIsCorrect(and_circuit, reference) + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_and_gate(self, num_variables, flags): + """Test correctness of the AndGate.""" + and_gate = AndGate(num_variables, flags) + flags = flags or [1] * num_variables + + def reference(bits): + flagged = [] + for flag, bit in zip(flags, bits): + if flag < 0: + flagged += [1 - bit] + elif flag > 0: + flagged += [bit] + return np.all(flagged) + + self.assertBooleanFunctionIsCorrect(and_gate, reference) + + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_and_gate_inverse(self, num_variables, flags): + """Test correctness of the AND-gate inverse.""" + and_gate = AndGate(num_variables, flags) + and_gate_inverse = and_gate.inverse() + self.assertEqual(Operator(and_gate), Operator(and_gate_inverse).adjoint()) + + @data( + (2, None), + (2, [-1, 1]), + (5, [0, 0, -1, 1, -1]), + (5, [-1, 0, 0, 1, 1]), + ) + @unpack + def test_and_equivalence(self, num_variables, flags): + """Test that AND-circuit and AND-gate yield equal operators + (when not using ancilla qubits). + """ + and_gate = AndGate(num_variables, flags) + and_circuit = AND(num_variables, flags) + self.assertEqual(Operator(and_gate), Operator(and_circuit)) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 220478159ee3..ba2546b2e8a5 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -311,6 +311,10 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_SingletonGateOverrides", "_SingletonControlledGateOverrides", "QFTGate", + "AndGate", + "OrGate", + "BitwiseXorGate", + "InnerProductGate", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get