Source code for qolumbina.programs.grover.grover_operator

# The original version of the following code is sourced from Qiskit circuit 
# library and involve the following files for dependency decoupling:
# https://github.com/Qiskit/qiskit/blob/stable/2.3/qiskit/circuit/library/grover_operator.py#L27-L285
#
# This program is adapted for use as a benchmark in controlled software testing experiments.
# Modifications made to the original code include (for Apache License 2.0):
# - Dependency decoupling: import the local Diagonal class to avoid circular imports from 
#   external repo.
# - Refactored to expose unified program interfaces:
#   - We adopt the functional interface style for benchmark registration, as it focuses more on the functionality
#     being provided rather than the inherent class structure with other components.
#   - `def grover_operator()` -> `class GroverOperator(QuantumCircuit)`
#   - Refine the statevector oracle handling to ensure compatibility with numpy arrays.
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The Grover operator."""
 
from __future__ import annotations
from typing import List, Optional, Union
import numpy

from qiskit.circuit import QuantumCircuit, AncillaQubit
from qiskit.exceptions import QiskitError
from qiskit.quantum_info import Statevector, Operator, DensityMatrix
from qiskit.utils.deprecation import deprecate_func
from qiskit.circuit.library import MCXGate  # This is a basic gate dependency and is kept here

import numpy as np
 
from ..diagonal import Diagonal

# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
    Path(__file__).stem,
    family=Path(__file__).resolve().parent.name,
    description="Construct the Grover operator circuit",
    class_name="GroverOperator",
    source={
        "repo": "https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f",
        "file": "qiskit/circuit/library/grover_operator.py",
        "sdk": "Qiskit",
        "available_doc": True
    },
    testability_refactoring=[
        "Dependency decoupling",
        "Structure reorganization"
    ],
)
def create_grover_operator(
    oracle: QuantumCircuit | Statevector, 
    state_preparation: QuantumCircuit | None = None,
    zero_reflection: QuantumCircuit | DensityMatrix | Operator | None = None,
    reflection_qubits: List[int] | None = None,
) -> QuantumCircuit:
    return GroverOperator(
        oracle=oracle,
        state_preparation=state_preparation,
        zero_reflection=zero_reflection,
        reflection_qubits=reflection_qubits,
    )


[docs] class GroverOperator(QuantumCircuit): r"""Grover operator circuit.""" def __init__( self, oracle: QuantumCircuit | Statevector, state_preparation: QuantumCircuit | None = None, zero_reflection: QuantumCircuit | DensityMatrix | Operator | None = None, reflection_qubits: list[int] | None = None, insert_barriers: bool = False, name: str = "Q", ): r""" Args: oracle: The phase oracle implementing a reflection about the bad state. Note that this is not a bitflip oracle, see the docstring for more information. state_preparation: The operator preparing the good and bad state. For Grover's algorithm, this is a n-qubit Hadamard gate and for amplitude amplification or estimation the operator :math:`\mathcal{A}`. If ``None``, a layer of Hadamard gates is used on ``reflection_qubits``. zero_reflection: The reflection about the zero state, :math:`\mathcal{S}_0`. If ``None``, a default implementation is used. reflection_qubits: Qubits on which the zero reflection acts on. If `None`, all qubits of the oracle are used. insert_barriers: Whether barriers should be inserted between the reflections and A. name: The name of the circuit. """ super().__init__(name=name) # store inputs self._oracle = oracle self._reflection_qubits = reflection_qubits self._state_preparation = state_preparation self._insert_barriers = insert_barriers self._zero_reflection = zero_reflection self.name = name # build circuit self._build() def _build(self) -> None: # We inherit the ancillas/qubits structure from the oracle, if it is given as circuit. if isinstance(self._oracle, QuantumCircuit): circuit = self._oracle.copy_empty_like(name=self.name, vars_mode="drop") else: circuit = QuantumCircuit(self._oracle.num_qubits, name=self.name) # (1) Add the oracle. # If the oracle is given as statevector, turn it into a circuit that implements the # reflection about the state. # Append a quantum gate -> compose to the circuit if isinstance(self._oracle, Statevector): diagonal = Diagonal((-1) ** self._oracle.data) # type: ignore circuit.compose(diagonal, inplace=True) else: circuit.compose(self._oracle, inplace=True) if self._insert_barriers: circuit.barrier() # (2) Add the inverse state preparation. # For this we need to know the target qubits that we apply the zero reflection to. # If the reflection qubits are not given, we assume they are the qubits that are not # of type ``AncillaQubit`` in the oracle. if self._reflection_qubits is None: self.reflection_qubits = [ i for i, qubit in enumerate(circuit.qubits) if not isinstance(qubit, AncillaQubit) ] if self._state_preparation is None: circuit.h(self.reflection_qubits) # H is self-inverse else: circuit.compose(self._state_preparation.inverse(), inplace=True) if self._insert_barriers: circuit.barrier() # (3) Add the zero reflection. if self._zero_reflection is None: num_reflection = len(self.reflection_qubits) circuit.x(self.reflection_qubits) if num_reflection == 1: circuit.z( self.reflection_qubits[0] ) # MCX does not support 0 controls, hence this is separate else: mcx = MCXGate(num_reflection - 1) circuit.h(self.reflection_qubits[-1]) circuit.append(mcx, self.reflection_qubits) circuit.h(self.reflection_qubits[-1]) circuit.x(self.reflection_qubits) elif isinstance(self._zero_reflection, (Operator, DensityMatrix)): diagonal = Diagonal(self._zero_reflection.data.diagonal()) # type: ignore circuit.compose(diagonal, inplace=True) else: circuit.compose(self._zero_reflection, inplace=True) if self._insert_barriers: circuit.barrier() # (4) Add the state preparation. if self._state_preparation is None: circuit.h(self.reflection_qubits) else: circuit.compose(self._state_preparation, inplace=True) # minus sign circuit.global_phase = numpy.pi # Compose built circuit into self self.add_register(*circuit.qregs) self.compose(circuit, inplace=True)