"""Quantum Phase Estimation (QPE) circuit fragment.
Estimates the phase :math:`\\varphi \\in [0, 1)` of a unitary's eigenvalue
:math:`e^{2\\pi i\\varphi}` on a known eigenstate, using ``n_precision``
ancilla qubits encoding the binary fraction of :math:`\\varphi`.
The ``unitary_circuit`` is interpreted as a single application of :math:`U`
on a system register; QPE applies controlled :math:`U^{2^k}` for
``k = 0, 1, ..., n_precision - 1``. Internally the controlled powers are
implemented by repeating the controlled :math:`U` ``2^k`` times — exact but
not necessarily efficient. Callers wanting efficient :math:`U^{2^k}` (e.g.
HHL or Shor) should pre-compute and pass a custom callable via
``controlled_power``.
Layout convention
-----------------
- Qubits ``[0 .. n_system - 1]`` hold the system register.
- Qubits ``[n_system .. n_system + n_precision - 1]`` hold the precision
register; qubit ``n_system`` is the *most* significant bit.
- The eigenstate is prepared by ``state_prep``; if ``None`` the system
register is left in :math:`|0\\rangle^{\\otimes n_{\\text{system}}}`.
- The inverse QFT (no swaps) is applied to the precision register.
- ``MEASURE`` instructions are appended on the precision register so that
the resulting integer ``m`` decoded as bitstring ``b_{n-1} ... b_0``
satisfies :math:`\\tilde{\\varphi} = m / 2^{n_{\\text{precision}}}`.
"""
from __future__ import annotations
__all__ = ["qpe_circuit", "qpe_example"]
from collections.abc import Callable
from uniqc.circuit_builder import Circuit
from .amplitude_estimation import _copy_circuit_gates_controlled
def _controlled_phase(fragment: Circuit, control: int, target: int, theta: float) -> None:
"""Apply controlled-phase :math:`CP(\\theta) = \\text{diag}(1,1,1,e^{i\\theta})`.
Decomposed (up to a global phase) as
``RZ(θ/2)_c · CNOT(c,t) · RZ(-θ/2)_t · CNOT(c,t) · RZ(θ/2)_t``.
"""
fragment.rz(control, theta / 2)
fragment.cnot(control, target)
fragment.rz(target, -theta / 2)
fragment.cnot(control, target)
fragment.rz(target, theta / 2)
def _inverse_qft_in_place(
fragment: Circuit,
qubits: list[int],
) -> None:
"""Apply textbook :math:`\\text{QFT}^\\dagger` (with bit-reversal swaps) on ``qubits``.
Uses true controlled-phase gates so that the integer ``m`` decoded from
the precision register's measurement reads naturally with cbit ``k``
holding bit ``k`` of ``m``.
"""
import math
n = len(qubits)
for j in reversed(range(n)):
for k in reversed(range(j + 1, n)):
angle = -math.pi / (2 ** (k - j))
_controlled_phase(fragment, qubits[k], qubits[j], angle)
fragment.h(qubits[j])
for i in range(n // 2):
fragment.swap(qubits[i], qubits[n - 1 - i])
[docs]
def qpe_circuit(
n_precision: int,
unitary_circuit: Circuit,
*,
state_prep: Circuit | None = None,
controlled_power: Callable[[Circuit, Circuit, int, int], None] | None = None,
measure: bool = True,
) -> Circuit:
"""Build a Quantum Phase Estimation circuit fragment.
Args:
n_precision: Number of precision qubits (output bits of the phase
estimate). Must be ``>= 1``.
unitary_circuit: A :class:`Circuit` representing one application of
the unitary :math:`U` on the system register (qubit indices
``0 .. n_system - 1``). The number of qubits in this fragment
sets ``n_system``.
state_prep: Optional :class:`Circuit` that prepares the eigenstate on
the system register. Must reference the same system qubits as
``unitary_circuit``. If ``None``, the system register starts in
:math:`|0\\rangle^{\\otimes n_{\\text{system}}}`.
controlled_power: Optional override to supply an efficient
implementation of controlled :math:`U^{2^k}`. Signature::
controlled_power(fragment, unitary, control_qubit, power)
where ``power == 2 ** k``. When ``None`` (the default) the
controlled :math:`U` is repeated ``power`` times — correct but
potentially slow for large ``n_precision``.
measure: If ``True`` (default), append ``MEASURE`` instructions on
the precision register at the end. Set ``False`` if you want to
chain QPE into a larger circuit before measuring.
Returns:
Fresh :class:`Circuit` with ``n_system + n_precision`` qubits.
Raises:
ValueError: ``n_precision < 1`` or ``unitary_circuit`` is empty.
"""
if n_precision < 1:
raise ValueError("qpe_circuit requires n_precision >= 1")
n_system = unitary_circuit.max_qubit + 1
if n_system < 1:
raise ValueError("qpe_circuit: unitary_circuit must contain at least one qubit")
system_qubits = list(range(n_system))
precision_qubits = list(range(n_system, n_system + n_precision))
fragment = Circuit()
if state_prep is not None:
fragment.add_circuit(state_prep)
for q in precision_qubits:
fragment.h(q)
for k, ctrl in enumerate(precision_qubits):
power = 1 << k # 2 ** k
if controlled_power is not None:
controlled_power(fragment, unitary_circuit, ctrl, power)
else:
for _ in range(power):
_copy_circuit_gates_controlled(unitary_circuit, fragment, ctrl)
_inverse_qft_in_place(fragment, precision_qubits)
if measure:
for q in precision_qubits:
fragment.measure(q)
return fragment
[docs]
def qpe_example() -> Circuit:
"""Return a small QPE demo: estimate the T-gate phase (1/8) with 4 precision bits."""
import math
u = Circuit()
u.rz(0, math.pi / 4) # T-gate-like phase on |1⟩
state_prep = Circuit()
state_prep.x(0) # eigenstate |1⟩
return qpe_circuit(
n_precision=4,
unitary_circuit=u,
state_prep=state_prep,
)