"""Deutsch-Jozsa algorithm circuit and oracle builder.
The oracular convention here (per the design notes in the project README) is:
- :func:`deutsch_jozsa_oracle` returns a fresh ``Circuit`` (the oracle).
- :func:`deutsch_jozsa_circuit` accepts a *quantum-circuit* oracle as its
argument and returns a fresh full-DJ ``Circuit`` (fragment style).
A legacy in-place form ``deutsch_jozsa_circuit(circuit, oracle, qubits)`` is
preserved as a deprecated dispatch and emits :class:`DeprecationWarning`.
"""
__all__ = ["deutsch_jozsa_circuit", "deutsch_jozsa_oracle", "deutsch_jozsa_example"]
import warnings
from uniqc._error_hints import format_enriched_message
from uniqc.circuit_builder import Circuit
[docs]
def deutsch_jozsa_oracle(
qubits: list[int],
balanced: bool = True,
target_bits: list[int] | None = None,
) -> Circuit:
r"""Build a Deutsch-Jozsa oracle circuit.
See module docstring; behaviour is unchanged from previous releases —
this is already a fragment-style API (returns a fresh ``Circuit``).
Args:
qubits: Data-qubit indices (explicit list, no default).
balanced: If ``True``, build a balanced oracle; otherwise constant.
target_bits: Data-qubit indices (positions within *qubits*) that
control the ancilla flip. Only used when *balanced* is ``True``.
``None`` means all data qubits.
Returns:
A new :class:`Circuit` containing the oracle gates.
"""
if not isinstance(qubits, list):
raise TypeError(format_enriched_message("qubits must be a list of qubit indices", "circuit_validation"))
if len(qubits) < 1:
raise ValueError(format_enriched_message("qubits must contain at least 1 data qubit", "circuit_validation"))
n_qubits = len(qubits)
ancilla = max(qubits) + 1
oracle = Circuit()
if not balanced:
return oracle
if target_bits is None:
target_bits = list(range(n_qubits))
for idx in target_bits:
if idx < 0 or idx >= n_qubits:
raise ValueError(
format_enriched_message(
f"target_bit {idx} out of range for {n_qubits} data qubits", "circuit_validation"
)
)
oracle.cnot(qubits[idx], ancilla)
return oracle
def _build_dj_fragment(
*,
oracle: Circuit,
qubits: list[int],
ancilla: int | None = None,
) -> Circuit:
if not isinstance(qubits, list):
raise TypeError(format_enriched_message("qubits must be a list of qubit indices", "circuit_validation"))
if len(qubits) < 1:
raise ValueError(format_enriched_message("qubits must contain at least 1 data qubit", "circuit_validation"))
n_data = len(qubits)
if ancilla is None:
ancilla = max(qubits) + 1
if oracle.qubit_num > 0 and oracle.qubit_num != n_data + 1:
raise ValueError(
format_enriched_message(
f"Oracle acts on {oracle.qubit_num} qubits, expected {n_data + 1} (data + ancilla)",
"circuit_validation",
)
)
fragment = Circuit()
for q in qubits:
fragment.h(q)
fragment.x(ancilla)
fragment.h(ancilla)
fragment.add_circuit(oracle)
for q in qubits:
fragment.h(q)
fragment.measure(*qubits)
return fragment
[docs]
def deutsch_jozsa_circuit(
*args,
qubits: list[int] | None = None,
ancilla: int | None = None,
oracle: Circuit | None = None,
):
r"""Build (or apply) the Deutsch-Jozsa algorithm circuit.
Two calling conventions are supported:
.. code-block:: python
# Fragment style (recommended):
ora = deutsch_jozsa_oracle(qubits=[0, 1, 2], balanced=True)
circuit = deutsch_jozsa_circuit(ora, qubits=[0, 1, 2]) # returns Circuit
# Legacy in-place style (deprecated):
c = Circuit()
deutsch_jozsa_circuit(c, ora, qubits=[0, 1, 2], ancilla=3) # mutates c
Args:
*args: Either ``(oracle: Circuit, ...)`` (fragment) or
``(circuit: Circuit, oracle: Circuit, ...)`` (deprecated in-place).
qubits: Data-qubit indices.
ancilla: Ancilla qubit index. ``None`` means ``max(qubits) + 1``.
oracle: The oracle ``Circuit`` (positional or keyword).
Returns:
A fresh :class:`Circuit` in fragment mode; ``None`` in legacy mode.
"""
# Resolve dispatch
if len(args) == 0:
if oracle is None:
raise TypeError(
format_enriched_message(
"deutsch_jozsa_circuit requires an oracle Circuit argument", "circuit_validation"
)
)
return _build_dj_fragment(oracle=oracle, qubits=qubits, ancilla=ancilla)
if len(args) == 1:
first = args[0]
# Fragment style: first arg IS the oracle
if oracle is None:
return _build_dj_fragment(oracle=first, qubits=qubits, ancilla=ancilla)
# Legacy: first arg is the in-place circuit
warnings.warn(
"deutsch_jozsa_circuit(circuit, oracle=...) (in-place form) is deprecated. "
"Use deutsch_jozsa_circuit(oracle, qubits=...) and add_circuit().",
DeprecationWarning,
stacklevel=2,
)
fragment = _build_dj_fragment(oracle=oracle, qubits=qubits, ancilla=ancilla)
first.add_circuit(fragment)
return None
if len(args) >= 2:
# Legacy positional: (circuit, oracle, qubits=..., ancilla=...)
circuit_in, ora = args[0], args[1]
warnings.warn(
"deutsch_jozsa_circuit(circuit, oracle, ...) (in-place form) is deprecated. "
"Use deutsch_jozsa_circuit(oracle, qubits=...) and add_circuit().",
DeprecationWarning,
stacklevel=2,
)
fragment = _build_dj_fragment(oracle=ora, qubits=qubits, ancilla=ancilla)
circuit_in.add_circuit(fragment)
return None
[docs]
def deutsch_jozsa_example() -> Circuit:
"""Return a 3-qubit balanced-DJ algorithm circuit for tests/docs."""
ora = deutsch_jozsa_oracle(qubits=[0, 1, 2], balanced=True)
return deutsch_jozsa_circuit(ora, qubits=[0, 1, 2])