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
preserved as dual-mode dispatchers: pass an integer to get a fresh fragment;
pass an existing ``Circuit`` (deprecated) to mutate it in place.
"""

__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._compat import dispatch_circuit_fragment
from uniqc.algorithms.core.circuits.dicke_state import dicke_state_circuit
from uniqc.circuit_builder import Circuit


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(first_arg=None, qubits: list[int] | None = None): r"""Prepare a GHZ state :math:`(|0\ldots0\rangle + |1\ldots1\rangle)/\sqrt 2`. Two calling conventions: .. code-block:: python # Fragment style (recommended): c = ghz_state(3) # returns Circuit c = ghz_state(3, qubits=[1, 2, 4]) # use offset qubits # Legacy in-place style (deprecated): c = Circuit(3) ghz_state(c) # mutates c in place Args: first_arg: Either an integer ``n_qubits`` (fragment mode) or a :class:`Circuit` (deprecated in-place mode). qubits: Qubit indices. Returns: A fresh :class:`Circuit` in fragment mode; ``None`` in legacy mode. """ return dispatch_circuit_fragment( name="ghz_state", fragment_builder=_build_ghz_fragment, first_arg=first_arg, legacy_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")) # Build a fresh circuit and use the (already-fragment-style) # ``dicke_state_circuit`` to populate it with k=1. fragment = Circuit() dicke_state_circuit(fragment, k=1, qubits=qubits) # legacy in-place; safe on fragment return fragment
[docs] def w_state(first_arg=None, qubits: list[int] | None = None): r"""Prepare a W state — equal superposition of single-excitation basis states. See :func:`ghz_state` for the dual-mode signature contract. """ return dispatch_circuit_fragment( name="w_state", fragment_builder=_build_w_fragment, first_arg=first_arg, legacy_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( first_arg=None, qubits: list[int] | None = None, edges: list[tuple[int, int]] | None = None, ): r"""Prepare a cluster (graph) state via :math:`H^{\otimes n}` + CZ on each edge. See :func:`ghz_state` for the dual-mode signature contract. ``edges`` defaults to a linear nearest-neighbour chain. """ return dispatch_circuit_fragment( name="cluster_state", fragment_builder=_build_cluster_fragment, first_arg=first_arg, legacy_qubits=qubits, extra_kwargs={"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), }