Source code for uniqc.algorithms.core.circuits.entangled_states

"""Entangled state preparation circuits: GHZ, W, and Cluster states.

All three follow the *circuit fragment* design (see
the design notes in the project README). The canonical APIs are:

- ``ghz_state_circuit(n_qubits, qubits=None) -> Circuit``
- ``w_state_circuit(n_qubits, qubits=None) -> Circuit``
- ``cluster_state_circuit(n_qubits, qubits=None, edges=None) -> Circuit``

The shorter names ``ghz_state``, ``w_state`` and ``cluster_state`` are
fragment-style aliases: pass an integer ``n_qubits`` to get a fresh fragment.
"""

__all__ = [
    "ghz_state",
    "w_state",
    "cluster_state",
    "ghz_state_circuit",
    "w_state_circuit",
    "cluster_state_circuit",
]


from uniqc._error_hints import format_enriched_message
from uniqc.algorithms.core.circuits.dicke_state import _build_dicke_fragment
from uniqc.circuit_builder import Circuit


def _infer_n_qubits(name: str, n_qubits: int | None, qubits: list[int] | None) -> int:
    """Infer ``n_qubits`` from a qubits list when not given explicitly."""
    if n_qubits is None:
        if not qubits:
            raise ValueError(f"{name}(...) requires either an integer n_qubits or a non-empty qubits list.")
        n_qubits = max(qubits) + 1
    return n_qubits


def _build_ghz_fragment(*, n_qubits: int, qubits: list[int] | None = None) -> Circuit:
    if qubits is None:
        qubits = list(range(n_qubits))
    if len(qubits) < 2:
        raise ValueError(format_enriched_message("ghz_state requires at least 2 qubits", "circuit_validation"))
    fragment = Circuit()
    fragment.h(qubits[0])
    for i in range(len(qubits) - 1):
        fragment.cnot(qubits[i], qubits[i + 1])
    return fragment


[docs] def ghz_state(n_qubits: int | None = None, qubits: list[int] | None = None) -> Circuit: r"""Prepare a GHZ state :math:`(|0\ldots0\rangle + |1\ldots1\rangle)/\sqrt 2`. .. code-block:: python c = ghz_state(3) # returns Circuit c = ghz_state(3, qubits=[1, 2, 4]) # use offset qubits Args: n_qubits: Number of qubits. May be ``None`` if ``qubits`` is given, in which case it is inferred as ``max(qubits) + 1``. qubits: Qubit indices. Returns: A fresh :class:`Circuit` containing the preparation fragment. """ n_qubits = _infer_n_qubits("ghz_state", n_qubits, qubits) return _build_ghz_fragment(n_qubits=n_qubits, qubits=qubits)
[docs] def ghz_state_circuit(n_qubits: int, qubits: list[int] | None = None) -> Circuit: """Fragment-style alias of :func:`ghz_state` (always returns a fresh ``Circuit``).""" return _build_ghz_fragment(n_qubits=n_qubits, qubits=qubits)
def _build_w_fragment(*, n_qubits: int, qubits: list[int] | None = None) -> Circuit: if qubits is None: qubits = list(range(n_qubits)) if len(qubits) < 2: raise ValueError(format_enriched_message("w_state requires at least 2 qubits", "circuit_validation")) # A W state is the k=1 Dicke state. return _build_dicke_fragment(n_qubits=n_qubits, qubits=qubits, k=1)
[docs] def w_state(n_qubits: int | None = None, qubits: list[int] | None = None) -> Circuit: r"""Prepare a W state — equal superposition of single-excitation basis states. See :func:`ghz_state` for the signature contract. """ n_qubits = _infer_n_qubits("w_state", n_qubits, qubits) return _build_w_fragment(n_qubits=n_qubits, qubits=qubits)
[docs] def w_state_circuit(n_qubits: int, qubits: list[int] | None = None) -> Circuit: """Fragment-style alias of :func:`w_state`.""" return _build_w_fragment(n_qubits=n_qubits, qubits=qubits)
def _build_cluster_fragment( *, n_qubits: int, qubits: list[int] | None = None, edges: list[tuple[int, int]] | None = None, ) -> Circuit: if qubits is None: qubits = list(range(n_qubits)) n = len(qubits) if n < 1: raise ValueError(format_enriched_message("cluster_state requires at least 1 qubit", "circuit_validation")) fragment = Circuit() for q in qubits: fragment.h(q) if edges is None: edges = [(i, i + 1) for i in range(n - 1)] for src_idx, tgt_idx in edges: if src_idx >= n or tgt_idx >= n: raise ValueError( format_enriched_message( f"Edge ({src_idx}, {tgt_idx}) out of range for {n} qubits", "circuit_validation" ) ) fragment.cz(qubits[src_idx], qubits[tgt_idx]) return fragment
[docs] def cluster_state( n_qubits: int | None = None, qubits: list[int] | None = None, edges: list[tuple[int, int]] | None = None, ) -> Circuit: r"""Prepare a cluster (graph) state via :math:`H^{\otimes n}` + CZ on each edge. See :func:`ghz_state` for the signature contract. ``edges`` defaults to a linear nearest-neighbour chain. """ n_qubits = _infer_n_qubits("cluster_state", n_qubits, qubits) return _build_cluster_fragment(n_qubits=n_qubits, qubits=qubits, edges=edges)
[docs] def cluster_state_circuit( n_qubits: int, qubits: list[int] | None = None, edges: list[tuple[int, int]] | None = None, ) -> Circuit: """Fragment-style alias of :func:`cluster_state`.""" return _build_cluster_fragment(n_qubits=n_qubits, qubits=qubits, edges=edges)
[docs] def entangled_states_example() -> dict: """Return a dict ``{ 'ghz': Circuit, 'w': Circuit, 'cluster': Circuit }`` for tests/docs.""" return { "ghz": ghz_state_circuit(3), "w": w_state_circuit(3), "cluster": cluster_state_circuit(4), }