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

"""Dicke state preparation circuit using the SCUC algorithm.

Reference:
    Bärtschi & Eidenbenz, "Deterministic Preparation of Dicke States",
    FCT 2019, arXiv:1904.07358.
"""

__all__ = ["dicke_state_circuit"]

import math

from uniqc._error_hints import format_enriched_message
from uniqc.circuit_builder import Circuit


def _gate_i(circuit: Circuit, q0: int, q1: int, n: int) -> None:
    """2-qubit Givens rotation in the {|10⟩, |01⟩} subspace.

    Angle θ = 2 * arccos(sqrt(1/n)).
    Decomposition: CX(q0,q1) · CRY(q1→q0, θ) · CX(q0,q1).
    """
    theta = 2.0 * math.acos(math.sqrt(1.0 / n))
    circuit.cnot(q0, q1)
    circuit.add_gate("RY", q0, params=theta, control_qubits=[q1])
    circuit.cnot(q0, q1)


def _ccry(circuit: Circuit, c1: int, c2: int, target: int, theta: float) -> None:
    """Doubly-controlled RY gate, decomposed via Toffoli + CRY.

    Applies RY(theta) on *target* iff both c1 and c2 are |1⟩.
    UnifiedQuantum has no native ccry, so we use:
        CRY(c2→target, θ/2) · CCX(c1,c2,target) ·
        CRY(c2→target, -θ/2) · CCX(c1,c2,target)
    """
    circuit.add_gate("RY", target, params=theta / 2.0, control_qubits=[c2])
    circuit.toffoli(c1, c2, target)
    circuit.add_gate("RY", target, params=-theta / 2.0, control_qubits=[c2])
    circuit.toffoli(c1, c2, target)


def _gate_ii_l(circuit: Circuit, q0: int, q1: int, q2: int, l: int, n: int) -> None:
    """3-qubit controlled Givens rotation (gate_(ii)_l in SCUC).

    Angle θ = 2 * arccos(sqrt(l/n)).
    Decomposition: CX(q0,q2) · CCRY(q2,q1→q0, θ) · CX(q0,q2).
    """
    theta = 2.0 * math.acos(math.sqrt(float(l) / n))
    circuit.cnot(q0, q2)
    _ccry(circuit, q2, q1, q0, theta)
    circuit.cnot(q0, q2)


def _scs(circuit: Circuit, qubits: list[int], n: int, k: int) -> None:
    """One Split-and-Cyclic-Shift (SCS) unitary SCS_{n,k}.

    *qubits* must have length k+1 (indices q_0 … q_k).
    Applies gate_i on (qubits[k-1], qubits[k]) followed by
    gate_ii_l for l = 2 … k on (qubits[k-l], qubits[k-l+1], qubits[k]).
    """
    _gate_i(circuit, qubits[k - 1], qubits[k], n)
    for l in range(2, k + 1):
        _gate_ii_l(circuit, qubits[k - l], qubits[k - l + 1], qubits[k], l, n)


def _build_dicke_fragment(
    *,
    n_qubits: int,
    qubits: list[int] | None = None,
    k: int = 1,
) -> Circuit:
    if qubits is None:
        qubits = list(range(n_qubits))
    n = len(qubits)
    if k < 1 or k > n:
        raise ValueError(
            format_enriched_message(f"k must satisfy 1 <= k <= n (got k={k}, n={n})", "circuit_validation")
        )

    fragment = Circuit()
    # Use the verified state-vector-then-prepare implementation
    from uniqc.algorithms.core.state_preparation.dicke_state import (
        dicke_state as _dicke_state_reference,
    )

    _dicke_state_reference(fragment, qubits=qubits, k=k)
    return fragment


[docs] def dicke_state_circuit( first_arg=None, k: int = 1, qubits: list[int] | None = None, ) -> Circuit | None: r"""Build (or apply) a Dicke-state preparation fragment :math:`|D(n,k)\rangle`. Two calling conventions: .. code-block:: python # Fragment style (recommended): c = dicke_state_circuit(4, k=2) # returns Circuit # Legacy in-place style (deprecated): c = Circuit() dicke_state_circuit(c, k=2, qubits=[0, 1, 2, 3]) Args: first_arg: Either ``n_qubits: int`` (fragment) or ``circuit: Circuit`` (deprecated). k: Number of excitations. qubits: Qubit indices to use. Returns: Fresh :class:`Circuit` in fragment mode; ``None`` in legacy mode. """ from uniqc.algorithms._compat import dispatch_circuit_fragment return dispatch_circuit_fragment( name="dicke_state_circuit", fragment_builder=_build_dicke_fragment, first_arg=first_arg, legacy_qubits=qubits, extra_kwargs={"k": k}, )
[docs] def dicke_state_example() -> Circuit: """Return a 4-qubit ``|D(4,2)>`` Dicke state circuit for tests/docs.""" return dicke_state_circuit(4, k=2)