"""QDK/Chemistry PREPARE-SELECT-PREPARE 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 typing import Any
from qdk import qsharp
from qdk_chemistry.data import AlgorithmRef, Settings
from qdk_chemistry.data.circuit import Circuit, QsharpFactoryData
from qdk_chemistry.data.unitary_representation.base import UnitaryRepresentation
from qdk_chemistry.data.unitary_representation.containers.base import UnitaryContainer
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 CircuitMapper
__all__: list[str] = ["PSPMapper", "PSPMapperSettings"]
[docs]
class PSPMapperSettings(Settings):
"""Settings for the PSPMapper.
Attributes:
prepare: Algorithm reference for the PREPARE oracle state preparation.
Defaults to ``DensePureStatePreparation``.
"""
[docs]
def __init__(self):
"""Initialize the settings for PSPMapper."""
super().__init__()
self._set_default(
"prepare",
"algorithm_ref",
AlgorithmRef("state_prep", "dense_pure_state"),
"Algorithm for the PREPARE oracle. ",
)
[docs]
class PSPMapper(CircuitMapper):
r"""Circuit mapper using the PREPARE-SELECT-PREPARE pattern.
Composes a 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 PSPMapper."""
super().__init__()
self._settings = PSPMapperSettings()
[docs]
def name(self) -> str:
"""Return the algorithm name.
Returns:
str: ``"prepare_select_prepare"``.
"""
return "prepare_select_prepare"
[docs]
def type_name(self) -> str:
"""Return the algorithm type name.
Returns:
str: ``"circuit_mapper"``.
"""
return "circuit_mapper"
@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)
[docs]
def resolve_lcu(self, container: UnitaryContainer) -> tuple[LCUContainer, bool]:
"""Unwrap a container into its LCU data and whether it is a quantum walk.
Args:
container: The container held by the unitary representation.
Returns:
The LCU data and whether the container is a quantum walk.
Raises:
ValueError: If the container is neither an LCU nor an LCU walk.
"""
if isinstance(container, LCUWalkContainer):
return container.block_encoding, True
if isinstance(container, LCUContainer):
return container, False
raise ValueError(
f"Container type '{type(container).__name__}' is not supported. "
"PSPMapper requires LCUContainer or LCUWalkContainer."
)
[docs]
def build_prepare_select_ops(self, container: UnitaryContainer) -> tuple[Any, Any, int]:
"""Return the PREPARE and SELECT Q# oracles and the system register size.
Args:
container: The container held by the unitary representation.
Returns:
The PREPARE Q# callable, the SELECT Q# callable, and the system register size.
"""
lcu, _ = self.resolve_lcu(container)
if lcu.prepare is not None:
prepare_op = self._create_nested("prepare").run(lcu.prepare)._qsharp_op # noqa: SLF001
else:
prepare_op = QSHARP_UTILS.PrepSelPrep.NoOpPrepare
return prepare_op, self._build_pauli_select_op(lcu.select), lcu.select.num_target_qubits
def _run_impl(self, unitary: UnitaryRepresentation) -> Circuit:
r"""Construct the block-encoding circuit on the flat ``[system | ancilla]`` register.
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 block encoding.
"""
container = unitary.get_container()
lcu, use_quantum_walk = self.resolve_lcu(container)
prepare_op, select_op, num_system = self.build_prepare_select_ops(container)
qsharp_op = QSHARP_UTILS.PrepSelPrep.MakePrepSelPrepOp(prepare_op, select_op, num_system)
if use_quantum_walk:
reflection_op = QSHARP_UTILS.PrepSelPrep.MakeAncillaReflectionOp(num_system)
qsharp_op = QSHARP_UTILS.PrepSelPrep.MakeWalkOp(qsharp_op, reflection_op)
if container.power != 1:
qsharp_op = QSHARP_UTILS.CircuitComposition.MakeRepeatedOp(
"PSPWalk" if use_quantum_walk else "PrepSelPrep",
qsharp_op,
container.power,
)
qsharp_factory = QsharpFactoryData(
program=QSHARP_UTILS.PrepSelPrep.MakePrepSelPrepCircuit,
parameter={
"prepareOp": prepare_op,
"selectOp": select_op,
"numSystemQubits": num_system,
"numAncillaQubits": lcu.num_prepare_ancillas,
"power": container.power,
"useWalk": use_quantum_walk,
},
)
return Circuit(
qsharp_factory=qsharp_factory,
qsharp_op=qsharp_op,
num_qubits=num_system + lcu.num_prepare_ancillas,
)