Source code for qarp.blocks._state_preparation.cv_qram_state_block

import numpy as np

from .._block import SimpleBlock
from .._prepares_known_state import prepares_known_state


[docs] @prepares_known_state class CVQRAMStateBlock(SimpleBlock): def __init__( self, dataset: dict[tuple, float], target_qubits=None, name: str = "CVQRAM", ): """An object for constructing CV-QRAM state preparation method [1]. By using state preparation methods we are able to load a classical dataset into a quantum state and initialize a given wavefunction. Please, check CVO_QRAM for an optimized version of this method. Preparation of an n-qubit state requires additional n+1 ancilla qubits (2n+1 qubits in total). [1] de Veras, T. M., da Silva, L. D., & da Silva, A. J. (2022). Double sparse quantum state preparation. Quantum Information Processing, 21(6), 204. Args: dataset: a dictionary specifying the data to be loaded with tuples as keys and amplitudes as values. target_qubits: The target qubits will act on when added to a Block object. """ self.dataset = dataset self._data_n_qubits = self._verify_dataset(self.dataset) n = self._data_n_qubits total_qubits = 2 * n + 1 if target_qubits is None: target_qubits = list(range(total_qubits)) super().__init__( n_qubits=total_qubits, target_qubits=target_qubits, name=name, ) if len(target_qubits) < total_qubits: raise ValueError("Not enough qubits for this method.") def _verify_dataset(self, dataset: dict[tuple, float]) -> int: # Two-sided: an under-normalized dataset would leave the ancillas away # from |0> with the missing mass, contradicting ancilla_postselection=None. if abs(sum(abs(amp) ** 2 for amp in dataset.values()) - 1.0) > 1e-8: raise ValueError("The sum of squared amplitude moduli should be equal to 1") size_tuples = [len(i) for i, _ in dataset.items()] if len(set(size_tuples)) != 1: raise ValueError("All the tuples in the dataset should have the same size") n_qubits = size_tuples[0] return n_qubits
[docs] def build_vanilla(self): assert self.dataset, "No dataset was provided to the state preparation object." n = self._data_n_qubits # Layout: anc_u0[0], anc_u1[1], anc[2..n], q_mem[n+1..2n] → 2n+1 total q_u0 = 0 q_u1 = 1 q_anc = list(range(2, n + 1)) # indices 2..n (n-1 ancillas) q_mem = list(range(n + 1, 2 * n + 1)) # indices n+1..2n self.x(q_u1) norm = 1.0 for instance, amplitude in self.dataset.items(): _load_binary(self, instance, q_u1, q_mem) _load_amplitude_cn_u(self, amplitude, norm, q_u0, q_u1, q_anc, q_mem, n) _load_binary(self, instance, q_u1, q_mem) norm -= abs(amplitude**2)
@staticmethod def _calculate_angles(norm, amplitude): alpha, beta, phi = 0, 0, 0 phase = abs(amplitude**2) if (norm - phase) < 0: norm = phase cos_value = np.sqrt((norm - phase) / norm) if cos_value > 1: cos_value = 1 elif cos_value < -1: cos_value = -1 alpha = 2 * np.arccos(cos_value) beta = np.arccos(-amplitude.real / np.sqrt(abs(amplitude**2))) if amplitude.imag < 0: beta = 2 * np.pi - beta phi = -beta return alpha, beta, phi @property def state_qubits(self) -> tuple[int, ...]: """The memory register — the ancillas carry no part of the state.""" return tuple(range(self._data_n_qubits + 1, 2 * self._data_n_qubits + 1))
[docs] def target_statevector(self) -> np.ndarray: """The dataset amplitudes, indexed LSB over the memory register. The ancillas are restored to ``|0⟩`` deterministically, so ``ancilla_postselection`` stays ``None`` and the block is control-safe — provided the caller reads ``state_qubits`` rather than ``n_qubits``. ``validate_amplification_blocks`` is the documented consumer that still reads ``n_qubits`` (a deferred contract limit), so this block does not yet fit amplitude amplification without padding the oracle. """ psi = np.zeros(2**self._data_n_qubits, dtype=complex) for instance, amplitude in self.dataset.items(): psi[sum(int(bit) << i for i, bit in enumerate(instance))] = amplitude return psi
def _load_binary(block, instance, q_u1_idx: int, q_mem) -> None: for q_idx, q_bin in enumerate(instance): if q_bin == 1: block.cx(q_u1_idx, q_mem[q_idx]) else: block.x(q_mem[q_idx]) def _load_amplitude_cn_u( block, amplitude, norm, q_u0_idx: int, q_u1_idx: int, q_anc, q_mem, n: int ) -> None: alpha, beta, phi = CVQRAMStateBlock._calculate_angles(norm, complex(amplitude)) # Nielsen & Chuang p.184 multi-controlled unitary block.ccx(q_mem[0], q_mem[1], q_anc[0]) for j in range(2, n): block.ccx(q_mem[j], q_anc[j - 2], q_anc[j - 1]) block.cx(q_anc[n - 2], q_u0_idx) block.cu(q_u0_idx, q_u1_idx, alpha, beta, phi, 0.0) block.cx(q_anc[n - 2], q_u0_idx) for j in range(n - 1, 2 - 1, -1): block.ccx(q_mem[j], q_anc[j - 2], q_anc[j - 1]) block.ccx(q_mem[0], q_mem[1], q_anc[0])