Source code for qarp.blocks._state_preparation.synthesized_state_block
from typing import Dict, List, Optional, Union
import numpy as np
from qarp.blocks._block import SimpleBlock
from qarp.blocks._prepares_known_state import prepares_known_state
[docs]
@prepares_known_state
class SynthesizedStateBlock(SimpleBlock):
"""Pattern A leaf: amplitude-encode an arbitrary `2^n`-vector into a circuit.
Delegates to ``self.state_preparation(...)`` (qarpx C++ Möttönen synthesis)
in ``build_vanilla()``. Inputs are normalized; an all-zero amplitude vector
is rejected at construction.
"""
def __init__(
self,
n_qubits: int,
amplitudes: Union[List[complex], Dict[tuple, complex]],
target_qubits: Optional[List[int]] = None,
name: str = "SynthStateBlock",
):
"""
Args:
n_qubits: Number of qubits.
amplitudes: Either a list of `2^n` complex amplitudes, or a dict
mapping basis-state tuples (e.g. ``(0, 1, 1)``) to complex
amplitudes. Tuples are LSB-first (element `i` is qubit `i`,
§1) — the same convention as list indices and Sampler keys.
Normalized internally.
target_qubits, name: standard Block kwargs.
"""
super().__init__(
n_qubits,
target_qubits=target_qubits,
name=name,
)
self.amplitudes = self._process_amplitudes(amplitudes)
self._validate_inputs()
def _process_amplitudes(
self, amplitudes: Union[List[complex], Dict[tuple, complex]]
) -> List[complex]:
if isinstance(amplitudes, dict):
if not amplitudes:
raise ValueError("Amplitudes dictionary cannot be empty.")
amplitude_list: List[complex] = [0.0 + 0.0j] * (2**self.n_qubits)
for state_tuple, amp in amplitudes.items():
if len(state_tuple) != self.n_qubits:
raise ValueError(
f"State tuple {state_tuple} has length {len(state_tuple)}, "
f"expected {self.n_qubits}."
)
if not all(bit in (0, 1) for bit in state_tuple):
raise ValueError(
f"State tuple {state_tuple} contains invalid values; "
"only 0 and 1 are allowed."
)
# LSB-first (§1): tuple element i is qubit i, so index bit i.
idx = sum(bit * (2**i) for i, bit in enumerate(state_tuple))
amplitude_list[idx] = amp
return amplitude_list
return list(amplitudes)
def _validate_inputs(self) -> None:
if len(self.amplitudes) != 2**self.n_qubits:
raise ValueError(
f"Length of amplitudes list must be {2**self.n_qubits} for {self.n_qubits} qubits."
)
norm = sum(abs(a) ** 2 for a in self.amplitudes) ** 0.5
if norm < 1e-15:
raise ValueError("Amplitudes cannot all be zero.")
self.amplitudes = [a / norm for a in self.amplitudes]
[docs]
def build_vanilla(self) -> None:
self.state_preparation(self.amplitudes)
[docs]
def target_statevector(self) -> np.ndarray:
"""The normalized input amplitudes.
Definitional, not an independent oracle: this block's target *is* its
input, so the conformance check here only pins the synthesis to the
vector it was handed. What proves the Möttönen synthesis itself correct
lives in the synthesis tests (§18).
"""
return np.asarray(self.amplitudes, dtype=complex)