Source code for qarp.blocks._primitives.upccd_block
from typing import List, Optional
import numpy as np
from sympy import Symbol
from .._block import CompositeBlockBase, SimpleBlock
from . import GivensBlock
[docs]
class UPCCDBlock(CompositeBlockBase):
def __init__(
self,
basis_state: list[int],
t2: Optional[np.ndarray] = None,
threshold: float = 1e-4,
target_qubits: Optional[List[int]] = None,
name: str = "UPCCD",
):
"""Constructs a Unitary Coupled Cluster Paired Doubles circuit block.
Pattern B (composite) — composes a sequence of ``GivensBlock``
children (one per allowed paired-double excitation) followed by a
layer of CX gates that copies the alpha-channel amplitudes onto the
beta channel.
This block assumes that all odd qubits (i.e. beta spin channel) are
set to zero. Jordan-Wigner encoding is assumed. Paired double
excitations are implemented as Givens rotations as in Nam et al.,
npj Quantum Information (2020) 6:33, but with the opposite sign of
the parameter (for consistency with pCCD). See section H of the
same paper's Supplementary Information for the alpha→beta CNOT
layer.
Args:
basis_state: Occupation number vector representing the reference state.
t2: Optional numpy array of paired double excitation amplitudes.
threshold: Threshold below which amplitudes are ignored.
target_qubits: Specific qubits to apply the block to. If None, uses all qubits.
name: Optional custom name for the block.
"""
if len(basis_state) % 2 == 1:
raise ValueError("Even number of qubits expected")
super().__init__(
n_qubits=len(basis_state),
target_qubits=target_qubits,
name=name,
)
self.occupation_number_vector = basis_state
self.t2 = t2
self.threshold = threshold
onv = np.asarray(self.occupation_number_vector)
self.onv_odd = onv[::2]
onv_even = onv[1::2]
if not np.allclose(self.onv_odd, onv_even):
raise ValueError("Closed-shell reference expected")
self.num_spatials = self.onv_odd.shape[0]
self.num_electron_pairs = np.sum(self.onv_odd)
if np.sum(self.onv_odd[0 : self.num_electron_pairs]) != self.num_electron_pairs:
raise ValueError("Aufbau principle violated")
self.symbols: list[Symbol] = []
self.symbol_parameter_map: dict[Symbol, float] = {}
[docs]
def build_vanilla(self) -> None:
"""Build the UPCCD composite by adding one GivensBlock per allowed
paired-double, then a final alpha→beta CX layer."""
if self.t2 is not None:
if (
self.t2.shape[0] != self.num_electron_pairs
or self.t2.shape[1] != self.num_spatials - self.num_electron_pairs
):
raise ValueError("Dimensions of t2 incompatible with number of orbitals")
# ── Givens-rotation layer ──
symbols: list[Symbol] = []
for i in range(self.num_electron_pairs):
for j in range(self.num_electron_pairs, self.num_spatials):
symbol = Symbol(f"pd{i}_{j}")
if self.t2 is not None:
if abs(self.t2[i, j - self.num_electron_pairs]) < self.threshold:
continue
self.symbol_parameter_map[symbol] = self.t2[i, j - self.num_electron_pairs]
symbols.append(symbol)
givens = GivensBlock(theta=2 * symbol)
givens.target_qubits = [2 * i, 2 * j]
self.add_wired_child(givens)
self.symbols = symbols
# ── alpha → beta CX layer (copy amplitudes; closed-shell) ──
cx_layer = SimpleBlock(self.n_qubits, name="alpha_to_beta")
for i in range(self.num_spatials):
cx_layer.cx(2 * i, 2 * i + 1)
self.add_wired_child(cx_layer)