Source code for qarp.blocks._primitives.spa_block
from typing import List, Optional
from sympy import Symbol
from .. import CompositeBlock
from . import AGateBlock, RSPBlock
[docs]
class SPABlock(CompositeBlock):
def __init__(
self,
n_qubits: int,
n_layers: int,
real: bool,
linear: bool,
circular: bool,
target_qubits: Optional[List[int]] = None,
name: Optional[str] = None,
):
"""Layered Separable Pair Ansatz (SPA) block with configurable entanglement geometry.
SPABlock constructs a parameterized quantum circuit using separable pair gates
(RSP or A-gates) applied between qubit pairs in specified patterns. The architecture
supports linear and brickwork entanglement topologies, where each two-qubit gate can
generate arbitrary entanglement within a pair while maintaining a structured layered
format. This provides an efficient ansatz for quantum chemistry and optimization problems
with controllable entanglement depth and expressibility.
Args:
n_qubits: Number of qubits in the circuit.
n_layers: Number of SPA layers to stack.
real: If True, uses RSP gates (real-valued); if False, uses A-gates (complex-valued with phase).
linear: If True, uses linear entanglement; if False, uses brickwork entanglement.
circular: If True, applies entanglement with periodic boundary conditions.
target_qubits: Specific qubits to apply the block to. If None, uses all qubits.
name: Optional custom name for the block.
"""
self.n_layers = n_layers
self.real = real
self.linear = linear
self.circular = circular
self.blocks = []
if name is None:
name = f"SPA (n={n_layers})"
for layer_index in range(n_layers):
if not self.linear: # pairwise / brickwork
# odd
for i in range(n_qubits // 2 - 1):
q = 2 * i + 1
self.blocks += [self.get_entangler_block(q, q + 1, layer_index)]
# even
for i in range(n_qubits // 2):
q = 2 * i
self.blocks += [self.get_entangler_block(q, q + 1, layer_index)]
if self.circular:
self.blocks += [self.get_entangler_block(n_qubits - 1, 0, layer_index)]
else: # linear
for i in range(n_qubits - 1):
self.blocks += [self.get_entangler_block(i, i + 1, layer_index)]
if self.circular:
self.blocks += [self.get_entangler_block(n_qubits - 1, 0, layer_index)]
super().__init__(
n_qubits=n_qubits,
blocks=self.blocks,
target_qubits=target_qubits,
name=name,
)
[docs]
def get_entangler_block(self, i, j, layer_index):
if self.real:
theta = Symbol(f"spa_theta_{layer_index}_{i}_{j}")
entangler = RSPBlock(theta, target_qubits=[i, j])
# self.symbols += [theta]
else:
phi = Symbol(f"spa_phi_{layer_index}_{i}_{j}")
theta = Symbol(f"spa_theta_{layer_index}_{i}_{j}")
# self.symbols += [phi, theta]
entangler = AGateBlock(theta=theta, phi=phi, target_qubits=[i, j])
return entangler