Source code for qdk_chemistry.algorithms.controlled_circuit_mapper.controlled_psp_mapper

"""QDK/Chemistry PREPARE-SELECT-PREPARE controlled circuit mapper."""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from qdk import qsharp

from qdk_chemistry.data import AlgorithmRef
from qdk_chemistry.data.circuit import Circuit, QsharpFactoryData
from qdk_chemistry.data.unitary_representation.base import UnitaryRepresentation
from qdk_chemistry.data.unitary_representation.containers.block_encoding import LCUContainer, Select
from qdk_chemistry.data.unitary_representation.containers.quantum_walk import LCUWalkContainer
from qdk_chemistry.utils.qsharp import QSHARP_UTILS

from .base import ControlledCircuitMapper, ControlledCircuitMapperSettings

__all__: list[str] = ["ControlledPSPMapper", "ControlledPSPMapperSettings"]


[docs] class ControlledPSPMapperSettings(ControlledCircuitMapperSettings): """Settings for the ControlledPSPMapper. Attributes: prepare: Algorithm reference for the PREPARE oracle state preparation. Defaults to ``DensePureStatePreparation``. """
[docs] def __init__(self): """Initialize the settings for ControlledPSPMapper.""" super().__init__() self._set_default( "prepare", "algorithm_ref", AlgorithmRef("state_prep", "dense_pure_state"), "Algorithm for the PREPARE oracle state preparation. ", )
[docs] class ControlledPSPMapper(ControlledCircuitMapper): r"""Controlled circuit mapper using the PREPARE-SELECT-PREPARE pattern. Composes a controlled block encoding from: 1. **PREPARE** — amplitude-loading into the ancilla register, resolved via the ``prepare`` setting. Defaults to ``DensePureStatePreparation``. 2. **SELECT** — Pauli SELECT oracle applied on the system register, constructed directly from the block-encoding container's SELECT data. The two callables are stitched together by the Q# ``PrepSelPrep`` operation: .. math:: B[H] = \mathrm{PREPARE}^\dagger \cdot \mathrm{SELECT} \cdot \mathrm{PREPARE} When the input is an :class:`~qdk_chemistry.data.unitary_representation.containers.quantum_walk.LCUWalkContainer`, the block encoding is additionally wrapped with the reflection operator to form a quantum walk: .. math:: W = (2|0\rangle\langle 0| - I) \cdot B[H] """
[docs] def __init__(self): """Initialize the ControlledPSPMapper.""" super().__init__() self._settings = ControlledPSPMapperSettings()
[docs] def name(self) -> str: """Return the algorithm name. Returns: str: The name ``"prepare_select_prepare"``. """ return "prepare_select_prepare"
[docs] def type_name(self) -> str: """Return the algorithm type name. Returns: str: The type name ``"controlled_circuit_mapper"``. """ return "controlled_circuit_mapper"
def _run_impl(self, unitary: UnitaryRepresentation) -> Circuit: r"""Construct a controlled block-encoding circuit. Args: unitary: The unitary representation containing either an :class:`LCUContainer` (plain block encoding) or an :class:`LCUWalkContainer` (quantum walk). Returns: Circuit: A quantum circuit implementing the controlled block encoding. """ container = unitary.get_container() # Resolve container type → LCU data + dispatch flag if isinstance(container, LCUWalkContainer): lcu = container.block_encoding power = container.power use_quantum_walk = True elif isinstance(container, LCUContainer): lcu = container power = container.power use_quantum_walk = False else: raise ValueError( f"Container type '{unitary.get_container_type()}' is not supported. " "ControlledPSPMapper requires LCUContainer or LCUWalkContainer." ) control_indices = self._get_control_indices() if len(control_indices) != 1: raise ValueError("ControlledPSPMapper currently only supports a single control qubit.") # 1. PREPARE — build state-preparation oracle prepare_op = self._build_prepare_op(lcu) # 2. SELECT — build Pauli SELECT oracle select_op = self._build_pauli_select_op(lcu.select) # 3. Compose controlled circuit num_system = lcu.select.num_target_qubits num_ancilla = lcu.num_prepare_ancillas if use_quantum_walk: make_circuit = QSHARP_UTILS.PrepSelPrep.MakeControlledPSPWalkCircuit make_op = QSHARP_UTILS.PrepSelPrep.MakeControlledPSPWalkOp else: make_circuit = QSHARP_UTILS.PrepSelPrep.MakeControlledPrepSelPrepCircuit make_op = QSHARP_UTILS.PrepSelPrep.MakeControlledPrepSelPrepOp psp_parameters = { "prepareOp": prepare_op, "selectOp": select_op, "numSystemQubits": num_system, "numAncillaQubits": num_ancilla, "power": power, } qsharp_factory = QsharpFactoryData(program=make_circuit, parameter=psp_parameters) qsharp_op = make_op(prepare_op, select_op, num_system, num_ancilla, power) return Circuit(qsharp_factory=qsharp_factory, qsharp_op=qsharp_op) def _build_prepare_op(self, lcu: LCUContainer): """Build the PREPARE Q# operation from an LCU container. For the 0-ancilla case the wavefunction has 0 modes, producing a no-op. Args: lcu: The LCU container holding the prepare wavefunction. Returns: A Q# callable implementing the PREPARE oracle. """ if lcu.prepare is not None: prepare_algorithm = self._create_nested("prepare") prepare_circuit = prepare_algorithm.run(lcu.prepare) return prepare_circuit._qsharp_op # noqa: SLF001 return QSHARP_UTILS.PrepSelPrep.NoOpPrepare @staticmethod def _build_pauli_select_op(select: Select): """Build the Pauli SELECT Q# operation from a Select data object. Converts each controlled operation's Pauli string into Q# ``Pauli`` enums and packages them with sign phases into a ``PauliSelectParams`` struct. Args: select: The SELECT oracle data object containing controlled operations, phases, and qubit layout. Returns: A Q# callable implementing the Pauli SELECT oracle. """ pauli_terms: list[list[qsharp.Pauli]] = [] control_states: list[int] = [] for op in select.controlled_operations: base_paulis = [qsharp.Pauli.I] * select.num_target_qubits for i, pauli_char in enumerate(reversed(op.operation)): if pauli_char != "I": base_paulis[i] = getattr(qsharp.Pauli, pauli_char) pauli_terms.append(base_paulis) control_states.append(op.ctrl_state) phases = [int(s) for s in select.phases] select_params = QSHARP_UTILS.Select.PauliSelectParams( pauliTerms=pauli_terms, signs=phases, controlStates=control_states ) return QSHARP_UTILS.Select.MakeSelectOp(select_params)