Source code for qarp.blocks._primitives.qubitization_block

from typing import List, Optional, Union

import numpy as np

from qarp.operators import QubitOperator

from .._block import CompositeBlockBase
from .block_encoding_block import BlockEncodingBlock
from .reflection_block import ReflectionBlock


[docs] class QubitizationBlock(CompositeBlockBase): r"""Pattern B composite: qubitization walk operator built from a ``ReflectionBlock`` on the ancilla register followed by a ``BlockEncodingBlock``. The walk operator is ``W = R · BE`` where: * ``R = 2|0…0⟩⟨0…0| - I`` on the LCU-control register (the first ``num_controls`` qubits, where ``num_controls = ⌈log₂ N_LCU⌉``). * ``BE`` block-encodes ``A / λ`` on the full register. Iterating ``W`` realises a quantum walk whose spectrum encodes the eigenphases of the block-encoded operator, the foundation of QSP / QSVT. """ def __init__( self, A: Union[np.ndarray, QubitOperator], operator_name: str = "Operator", target_qubits: Optional[List[int]] = None, name: str = "Qubitization", ): """Args: A: Operator to qubitize, either an `np.ndarray` or a `openfermion.QubitOperator`. operator_name: Name forwarded to the inner ``BlockEncodingBlock``. target_qubits, name: standard Block kwargs. """ if not isinstance(A, (np.ndarray, QubitOperator)): raise TypeError("Expected a np.ndarray or QubitOperator in QubitizationBlock") self.A = A self.operator_name = operator_name # Construct a BE up-front for sizing and so consumers can read # ``self.BE.lambda_norm`` / ``self.BE.unitaries`` before .build() — # ``self.BE`` is stable public surface. self.BE = BlockEncodingBlock(A, name=operator_name) self.lambda_factor = self.BE.lambda_norm self.unitaries_qubits = list(range(self.BE.num_controls, self.BE.n_qubits)) super().__init__( n_qubits=self.BE.n_qubits, target_qubits=target_qubits, name=name, )
[docs] def build_vanilla(self) -> None: n_anc = self.BE.num_controls # 1. Reflection about |0…0⟩ on the ancilla register. refl = ReflectionBlock(n_anc) refl.target_qubits = list(range(n_anc)) self.add_wired_child(refl) # 2. Block encoding on the full register. We rebuild a fresh BE here # — the one stashed on ``self.BE`` is used only for sizing / metadata # exposure; building it twice is harmless (both produce identical # circuits) and keeps add_child semantics simple. be = BlockEncodingBlock(self.A, name=self.operator_name) be.target_qubits = list(range(self.n_qubits)) self.add_wired_child(be)