Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boolean circuits as gates #13333

Merged
merged 16 commits into from
Oct 31, 2024
Merged
11 changes: 11 additions & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
=====================
Expand Down Expand Up @@ -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 (
Expand Down
8 changes: 4 additions & 4 deletions qiskit/circuit/library/boolean_logic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 80 additions & 3 deletions qiskit/circuit/library/boolean_logic/inner_product.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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.

Expand All @@ -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
111 changes: 107 additions & 4 deletions qiskit/circuit/library/boolean_logic/quantum_and.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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
)
Loading