Skip to content

Commit

Permalink
Added PhaseOracleGate
Browse files Browse the repository at this point in the history
  • Loading branch information
gadial committed Feb 20, 2025
1 parent df94414 commit 991ded1
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 13 deletions.
3 changes: 2 additions & 1 deletion qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@
PhaseEstimation
GroverOperator
BitFlipOracleGate
PhaseOracleGate
PhaseOracle
PauliEvolutionGate
HamiltonianGate
Expand Down Expand Up @@ -671,7 +672,7 @@
from .iqp import IQP, iqp, random_iqp
from .phase_estimation import PhaseEstimation, phase_estimation
from .grover_operator import GroverOperator, grover_operator
from .phase_oracle import PhaseOracle
from .phase_oracle import PhaseOracle, PhaseOracleGate
from .bit_flip_oracle import BitFlipOracleGate
from .overlap import UnitaryOverlap, unitary_overlap
from .standard_gates import get_standard_gate_name_mapping
14 changes: 10 additions & 4 deletions qiskit/circuit/library/bit_flip_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@


class BitFlipOracleGate(Gate):
r"""Bit-flip Oracle.
r"""Bit-flip Oracle Gate.
The Bit-flip Oracle object constructs circuits for any arbitrary
The Bit-flip Oracle Gate object constructs circuits for any arbitrary
input logical expressions. A logical expression is composed of logical operators
`&` (logical `AND`), `|` (logical `OR`),
`~` (logical `NOT`), and `^` (logical `XOR`).
Expand Down Expand Up @@ -63,8 +63,14 @@ def __init__(
"""
self.boolean_expression = BooleanExpression(expression, var_order=var_order)
self.oracle = self.boolean_expression.synth(circuit_type="bit")

super().__init__(name="Bit-flip Oracle", num_qubits=self.oracle.num_qubits, params=[])
short_expr_for_name = (expression[:15] + "...") if len(expression) > 15 else expression

super().__init__(
name="Bit-flip Oracle",
num_qubits=self.oracle.num_qubits,
params=[],
label=short_expr_for_name,
)

def _define(self):
"""
Expand Down
109 changes: 108 additions & 1 deletion qiskit/circuit/library/phase_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from __future__ import annotations

from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumCircuit, Gate

from qiskit.synthesis.boolean.boolean_expression import BooleanExpression

Expand Down Expand Up @@ -127,3 +127,110 @@ def from_dimacs_file(cls, filename: str):
"""
expr = BooleanExpression.from_dimacs_file(filename)
return cls(expr)


class PhaseOracleGate(Gate):
r"""Phase PhaseOracleGate.
The Phase Oracle Gate object constructs circuits for any arbitrary
input logical expressions. A logical expression is composed of logical operators
`&` (logical `AND`), `|` (logical `OR`),
`~` (logical `NOT`), and `^` (logical `XOR`).
as well as symbols for literals (variables).
For example, `'a & b'`, and `(v0 | ~v1) & (~v2 & v3)`
are both valid string representation of boolean logical expressions.
A phase oracle for a boolean function `f(x)` performs the following
quantum operation:
.. math::
|x\rangle \mapsto (-1)^{f(x)}|x\rangle
For convenience, this oracle, in addition to parsing arbitrary logical expressions,
also supports input strings in the `DIMACS CNF format
<https://web.archive.org/web/20190325181937/https://www.satcompetition.org/2009/format-benchmarks2009.html>`__,
which is the standard format for specifying SATisfiability (SAT) problem instances in
`Conjunctive Normal Form (CNF) <https://en.wikipedia.org/wiki/Conjunctive_normal_form>`__,
which is a conjunction of one or more clauses, where a clause is a disjunction of one
or more literals. See :meth:`qiskit.circuit.library.phase_oracle.PhaseOracleGate.from_dimacs_file`.
From 16 variables on, possible performance issues should be expected when using the
default synthesizer.
"""

def __init__(
self,
expression: str,
var_order: list[str] | None = None,
) -> None:
"""
Args:
expression: A Python-like boolean expression.
var_order: A list with the order in which variables will be created.
(default: by appearance)
"""
self.boolean_expression = BooleanExpression(expression, var_order=var_order)
self.oracle = self.boolean_expression.synth(circuit_type="phase")
short_expr_for_name = (expression[:15] + "...") if len(expression) > 15 else expression

super().__init__(
name="Phase Oracle",
num_qubits=self.oracle.num_qubits,
params=[],
label=short_expr_for_name,
)

def _define(self):
"""
Defined by the synthesized phase oracle
"""
self.definition = self.oracle

@classmethod
def from_dimacs_file(cls, filename: str):
r"""Create a PhaseOracle from the string in the DIMACS format.
It is possible to build a PhaseOracle from a file in `DIMACS CNF format
<https://web.archive.org/web/20190325181937/https://www.satcompetition.org/2009/format-benchmarks2009.html>`__,
which is the standard format for specifying SATisfiability (SAT) problem instances in
`Conjunctive Normal Form (CNF) <https://en.wikipedia.org/wiki/Conjunctive_normal_form>`__,
which is a conjunction of one or more clauses, where a clause is a disjunction of one
or more literals.
The following is an example of a CNF expressed in the DIMACS format:
.. code:: text
c DIMACS CNF file with 3 satisfying assignments: 1 -2 3, -1 -2 -3, 1 2 -3.
p cnf 3 5
-1 -2 -3 0
1 -2 3 0
1 2 -3 0
1 -2 -3 0
-1 2 3 0
The first line, following the `c` character, is a comment. The second line specifies that
the CNF is over three boolean variables --- let us call them :math:`x_1, x_2, x_3`, and
contains five clauses. The five clauses, listed afterwards, are implicitly joined by the
logical `AND` operator, :math:`\land`, while the variables in each clause, represented by
their indices, are implicitly disjoined by the logical `OR` operator, :math:`lor`. The
:math:`-` symbol preceding a boolean variable index corresponds to the logical `NOT`
operator, :math:`lnot`. Character `0` (zero) marks the end of each clause. Essentially,
the code above corresponds to the following CNF:
:math:`(\lnot x_1 \lor \lnot x_2 \lor \lnot x_3)
\land (x_1 \lor \lnot x_2 \lor x_3)
\land (x_1 \lor x_2 \lor \lnot x_3)
\land (x_1 \lor \lnot x_2 \lor \lnot x_3)
\land (\lnot x_1 \lor x_2 \lor x_3)`.
Args:
filename: A file in DIMACS format.
Returns:
PhaseOracleGate: A quantum circuit with a phase oracle.
"""
expr = BooleanExpression.from_dimacs_file(filename)
return cls(expr)
71 changes: 66 additions & 5 deletions releasenotes/notes/boolean_expression_update-39c19edbbe71ba0d.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ upgrade_circuits:
but might not be synthesized as effectively as before.
:class:`.BitFlipOracleGate` was added as an alternative to
directly synthesizing :class:`.BooleanExpression`, as this
class is removed in Qiskit 2.0.
class is removed in Qiskit 2.0. A :class:`.PhaseOracleGate`
was added and will replace :class:`.PhaseOracle` in Qiskit 3.0.
The interface of :class:`.PhaseOracle` was simplified; it no longer
accepts a `synthesizer` parameter, and the `expression` parameter
Expand All @@ -31,19 +32,42 @@ upgrade_circuits:
features_circuits:
- |
The new :class:`.BitFlipOracleGate` has the same interface as
:class:`.PhaseOracle` (except the `evaluate_bitstring` method),
but synthesizes a bit flip oracle instead
The new :class:`.BitFlipOracleGate` and :class:`.PhaseOracleGate`
have the same interface as :class:`.PhaseOracle`
(except the `evaluate_bitstring` method). Bit-flip oracle gate
synthesizes a bit flip oracle instead
of a phase flip oracle, meaning it acts on one additional qubit
and can be seen a applying a controlled X operation, where the
control is the value of the expression encoded by the oracle.
.. code-block:: python
from qiskit import QuantumCircuit
from qiskit.circuit.library.bit_flip_oracle import BitFlipOracleGate
from qiskit.circuit.library.phase_oracle import PhaseOracleGate
qc = QuantumCircuit(5)
bool_expr = "(x0 & x1 | ~x2) & x4"
oracle = BitFlipOracleGate(bool_expr)
print(oracle)
qc.compose(oracle, inplace=True)
print(qc)
.. code-block:: text
┌─────────────────────┐
q_0: ┤0 ├
│ │
q_1: ┤1 ├
│ │
q_2: ┤2 (x0 & x1 | ~x2)... ├
│ │
q_3: ┤3 ├
│ │
q_4: ┤4 ├
└─────────────────────┘
.. code-block:: python
print(qc.decompose())
.. code-block:: text
Expand All @@ -57,3 +81,40 @@ features_circuits:
┌─┴─┐┌─┴─┐┌─┴─┐
q_4: ┤ X ├┤ X ├┤ X ├
└───┘└───┘└───┘
.. code-block:: python
qc = QuantumCircuit(5)
bool_expr = "(x0 & x1 | ~x2) & x4"
oracle = PhaseOracleGate(bool_expr)
qc.compose(oracle, inplace=True)
print(qc)
.. code-block:: text
┌───────────────┐
q_0: ┤0 ├
│ │
q_1: ┤1 ├
│ Phase oracle │
q_2: ┤2 ├
│ │
q_3: ┤3 ├
└───────────────┘
q_4: ─────────────────
.. code-block:: python
print(qc.decompose())
.. code-block:: text
q_0: ─o──■──■─
│ │ │
q_1: ─┼──o──■─
│ │ │
q_2: ─o──o──┼─
│ │ │
q_3: ─■──■──■─
q_4: ─────────
64 changes: 63 additions & 1 deletion test/python/circuit/library/test_phase_and_bitflip_oracles.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from numpy import sqrt, isclose

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import PhaseOracle, BitFlipOracleGate
from qiskit.circuit.library import PhaseOracle, PhaseOracleGate, BitFlipOracleGate
from qiskit.quantum_info import Statevector
from test import QiskitTestCase # pylint: disable=wrong-import-order

Expand Down Expand Up @@ -97,6 +97,68 @@ def test_variable_order(self, expression, var_order, good_states):
self.assertListEqual(expected_invalid, result_invalid)


@ddt
class TestPhaseOracleGate(QiskitTestCase):
"""Test phase oracle object."""

@data(
("x | x", "01"),
("~x", "10"),
("x & y", "0001"),
("x & ~y", "0100"),
("(x0 & x1 | ~x2) ^ x4", "1111000100001110"),
("x & y ^ ( ~z1 | z2)", "1110000111101110"),
)
@unpack
def test_statevector(self, expression, truth_table):
"""Circuit generation"""
oracle = PhaseOracleGate(expression)
num_qubits = oracle.num_qubits
circuit = QuantumCircuit(num_qubits)
circuit.h(range(num_qubits))
circuit.compose(oracle, inplace=True)
statevector = Statevector.from_instruction(circuit)

valid_state = -1 / sqrt(2**num_qubits)
invalid_state = 1 / sqrt(2**num_qubits)

states = list(range(2**num_qubits))
good_states = [i for i in range(len(states)) if truth_table[i] == "1"]
expected_valid = [state in good_states for state in states]
result_valid = [isclose(statevector.data[state], valid_state) for state in states]

expected_invalid = [state not in good_states for state in states]
result_invalid = [isclose(statevector.data[state], invalid_state) for state in states]
self.assertListEqual(expected_valid, result_valid)
self.assertListEqual(expected_invalid, result_invalid)

@data(
("((A & C) | (B & D)) & ~(C & D)", None, [3, 7, 12, 13]),
("((A & C) | (B & D)) & ~(C & D)", ["A", "B", "C", "D"], [5, 7, 10, 11]),
)
@unpack
def test_variable_order(self, expression, var_order, good_states):
"""Circuit generation"""
oracle = PhaseOracleGate(expression, var_order=var_order)
num_qubits = oracle.num_qubits
circuit = QuantumCircuit(num_qubits)
circuit.h(range(num_qubits))
circuit.compose(oracle, inplace=True)
statevector = Statevector.from_instruction(circuit)

valid_state = -1 / sqrt(2**num_qubits)
invalid_state = 1 / sqrt(2**num_qubits)

states = list(range(2**num_qubits))
expected_valid = [state in good_states for state in states]
result_valid = [isclose(statevector.data[state], valid_state) for state in states]

expected_invalid = [state not in good_states for state in states]
result_invalid = [isclose(statevector.data[state], invalid_state) for state in states]
self.assertListEqual(expected_valid, result_valid)
self.assertListEqual(expected_invalid, result_invalid)


@ddt
class TestBitFlipOracleGate(QiskitTestCase):
"""Test bit-flip oracle object."""
Expand Down
2 changes: 1 addition & 1 deletion test/python/circuit/test_gate_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ def test_equivalence_phase(self, gate_class):
params[0] = 2
if gate_class.__name__ in ["PauliGate"]:
params = ["IXYZ"]
if gate_class.__name__ in ["BooleanExpression"]:
if gate_class.__name__ in ["BooleanExpression", "BitFlipOracleGate", "PhaseOracleGate"]:
params = ["x | y"]

gate = gate_class(*params)
Expand Down

0 comments on commit 991ded1

Please sign in to comment.