Source code for uniqc.algorithmics.circuits.deutsch_jozsa
"""Deutsch-Jozsa algorithm circuit and oracle builder."""
__all__ = ["deutsch_jozsa_circuit", "deutsch_jozsa_oracle"]
from typing import List, Optional
from uniqc.circuit_builder import Circuit
[docs]
def deutsch_jozsa_oracle(
qubits: List[int],
balanced: bool = True,
target_bits: Optional[List[int]] = None,
) -> Circuit:
r"""Build a Deutsch-Jozsa oracle circuit.
Generates either a **constant** oracle (maps all inputs to 0 or all to 1)
or a **balanced** oracle (maps half the inputs to 0 and half to 1).
* **Constant oracle**: does nothing (output = 0) or flips the ancilla
(output = 1). Here we implement the identity (output always 0).
* **Balanced oracle**: applies CNOT from each *target_bit* to the
ancilla. If ``target_bits`` is ``None``, all data qubits are used.
This yields a balanced function because the ancilla flips exactly
when an odd number of target bits are set.
The oracle acts on the data qubits in *qubits* plus one ancilla qubit
at ``max(qubits) + 1``.
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.
Raises:
TypeError: *qubits* is not a list.
ValueError: *qubits* is empty, or *target_bits* contains invalid indices.
Example:
>>> oracle = deutsch_jozsa_oracle(qubits=[0, 1, 2], balanced=True)
>>> oracle.qubit_num # 3 data + 1 ancilla
4
"""
if not isinstance(qubits, list):
raise TypeError("qubits must be a list of qubit indices")
if len(qubits) < 1:
raise ValueError("qubits must contain at least 1 data qubit")
n_qubits = len(qubits)
ancilla = max(qubits) + 1
oracle = Circuit()
if not balanced:
# Constant oracle: output is always 0 → identity on ancilla
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(
f"target_bit {idx} out of range for {n_qubits} data qubits"
)
oracle.cnot(qubits[idx], ancilla)
return oracle
[docs]
def deutsch_jozsa_circuit(
circuit: Circuit,
oracle: Circuit,
qubits: List[int],
ancilla: Optional[int] = None,
) -> None:
r"""Apply the Deutsch-Jozsa algorithm to the circuit.
The Deutsch-Jozsa algorithm determines whether a function
:math:`f: \{0,1\}^n \to \{0,1\}` is **constant** or **balanced**
using a single quantum query.
Circuit steps:
1. Initialise data qubits to :math:`|+\rangle` and ancilla to
:math:`|-\rangle` (via ``X`` then ``H``).
2. Apply the oracle.
3. Apply Hadamard on all data qubits.
4. Measure data qubits: all-zeros → constant, otherwise → balanced.
Args:
circuit: Quantum circuit to operate on (mutated in-place).
oracle: Oracle sub-circuit to embed. Must operate on
``len(qubits) + 1`` qubits (data + ancilla), or be empty
(constant-zero oracle).
qubits: Data-qubit indices (explicit list, no default).
ancilla: Ancilla qubit index. ``None`` means ``max(qubits) + 1``.
Raises:
TypeError: *qubits* is not a list.
ValueError: *qubits* is empty, or oracle qubit count mismatches.
Example:
>>> from uniqc.circuit_builder import Circuit
>>> from uniqc.algorithmics.circuits import (
... deutsch_jozsa_circuit, deutsch_jozsa_oracle,
... )
>>> n = 3
>>> oracle = deutsch_jozsa_oracle(qubits=list(range(n)), balanced=True)
>>> c = Circuit()
>>> deutsch_jozsa_circuit(c, oracle, qubits=list(range(n)), ancilla=n)
"""
if not isinstance(qubits, list):
raise TypeError("qubits must be a list of qubit indices")
if len(qubits) < 1:
raise ValueError("qubits must contain at least 1 data qubit")
n_data = len(qubits)
if ancilla is None:
ancilla = max(qubits) + 1
# Only validate oracle width when it has gates (empty constant oracle
# has qubit_num=0 but is still a valid DJ oracle for any n_data).
if oracle.qubit_num > 0 and oracle.qubit_num != n_data + 1:
raise ValueError(
f"Oracle acts on {oracle.qubit_num} qubits, "
f"expected {n_data + 1} (data + ancilla)"
)
# Step 1: |+⟩ on data qubits, |−⟩ on ancilla
for q in qubits:
circuit.h(q)
circuit.x(ancilla)
circuit.h(ancilla)
# Step 2: Apply oracle
circuit.add_circuit(oracle)
# Step 3: H on data qubits
for q in qubits:
circuit.h(q)
# Step 4: Measure data qubits
circuit.measure(*qubits)