Source code for qarp.blocks._state_preparation.hypergraph_state_block
from __future__ import annotations
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
import numpy as np
from qarp.blocks._block import SimpleBlock
from qarp.blocks._prepares_known_state import prepares_known_state
if TYPE_CHECKING:
from qarp.graphs._hypergraph import Hypergraph
[docs]
@prepares_known_state
class HypergraphStateBlock(SimpleBlock):
def __init__(
self,
hypergraph: Optional[Hypergraph] = None,
n_qubits: Optional[int] = None,
edges: Optional[List[Tuple[int, ...]]] = None,
target_qubits: Optional[List[int]] = None,
name: Optional[str] = None,
):
"""Hypergraph state preparation block for multi-qubit entangled states.
HypergraphStateBlock prepares quantum states defined by hypergraphs, where vertices
represent qubits and hyperedges encode multi-qubit entangling operations. Starting
from an equal superposition (H^⊗n|0⟩^⊗n), controlled-Z gates are applied according
to the hypergraph structure, creating states with higher-order correlations beyond
pairwise entanglement. (See doi:10.1088/1367-2630/15/11/113022 for more details.)
Args:
hypergraph: A Hypergraph object defining the state structure. Takes precedence if provided.
n_qubits: Number of qubits (required if hypergraph is None).
edges: List of hyperedges, each a tuple of qubit indices. Last index is target for CnZ gate.
On this path the ``hypergraph`` attribute is the equivalent ``Hypergraph``
when hypernetx is installed and ``None`` otherwise.
target_qubits: The target qubits the underlying block will act on when added to a circuit.
name: Optional custom name for the block. Defaults to "HypergraphState({n_qubits}q)".
Raises:
TypeError: If hypergraph is provided but is not a Hypergraph instance,
or a vertex label is not a non-negative integer.
ValueError: If neither hypergraph nor both n_qubits and edges are provided.
"""
if hypergraph is not None:
from qarp.graphs import Hypergraph # lazy: hypernetx drags matplotlib
if not isinstance(hypergraph, Hypergraph):
raise TypeError(
f"Expected 'hypergraph' to be an instance of Hypergraph, got {type(hypergraph).__name__}."
)
# Vertices are qubit indices, so the size is the highest index + 1, not
# the vertex count; the check lives once, on Hypergraph.n_qubits.
resolved_n_qubits = hypergraph.n_qubits
resolved_edges: Union[List[Tuple[int, ...]], list[list[Any]]] = [
list(hypergraph.edges[idx]) for idx in hypergraph.edges
]
resolved_hypergraph: Optional[Hypergraph] = hypergraph
else:
if not isinstance(n_qubits, int) or not isinstance(edges, list):
raise ValueError(
"Either 'hypergraph' or both 'n_qubits' and 'edges' must be provided."
)
resolved_n_qubits = n_qubits
resolved_edges = edges
# The block reads only n_qubits and edges; the Hypergraph is a
# convenience that must not make a base install fail (hypernetx is
# the optional [hypergraph] extra).
try:
from qarp.graphs import Hypergraph # lazy: hypernetx drags matplotlib
except ImportError:
resolved_hypergraph = None
else:
resolved_hypergraph = Hypergraph(edges)
super().__init__(
resolved_n_qubits,
target_qubits=target_qubits,
name=name if name else f"HypergraphState({resolved_n_qubits}q)",
)
self.hypergraph = resolved_hypergraph
self.edges = resolved_edges
[docs]
def build_vanilla(self) -> None:
if self.n_qubits is None:
raise RuntimeError("n_qubits is undefined") # keeps mypy happy
# H on every qubit (inline HnBlock) — bulk emit.
self.h(list(range(self.n_qubits)))
# CnZ per edge: decomposed into available qarpx gates
for edge in self.edges:
qubits = list(edge)
n = len(qubits)
if n == 1:
self.z(qubits[0])
else:
self.mcz(qubits)
[docs]
def target_statevector(self) -> np.ndarray:
r"""``2^{-n/2} (-1)^{e(x)}``, with ``e(x)`` the number of hyperedges
whose qubits are all set in ``x``.
The order-1 case is included: a single-vertex edge is "fully set" iff
that bit is 1, which is exactly the ``Z`` the build emits.
"""
dim = 2**self.n_qubits
psi = np.full(dim, 2 ** (-self.n_qubits / 2), dtype=complex)
idx = np.arange(dim)
for edge in self.edges:
mask = sum(1 << int(q) for q in edge)
psi[(idx & mask) == mask] *= -1
return psi