Deutsch-Jozsa Algorithm
Determine whether a Boolean function is constant or balanced with a single quantum query using the Deutsch-Jozsa algorithm.
Problem
What is the Deutsch-Jozsa Problem?
You are given a black-box (oracle) function
- Constant --
for all inputs, or for all inputs. - Balanced --
for exactly half of all inputs and for the other half.
The task is to determine which type
Classical Complexity
On a classical computer, in the worst case you must evaluate
| Input bits | Total inputs | Classical worst case |
|---|---|---|
| 1 | 2 | 2 |
| 2 | 4 | 3 |
| 3 | 8 | 5 |
| 10 | 1,024 | 513 |
| 20 | 1,048,576 | 524,289 |
Classical complexity is
Quantum Speedup
The Deutsch-Jozsa algorithm solves the problem with exactly one quantum query, regardless of
This was one of the first quantum algorithms to demonstrate an exponential separation between quantum and classical query complexity, albeit for a promise problem rather than a general one.
Historical Context
- 1985 -- David Deutsch proposed the original algorithm for
, showing that a quantum computer can distinguish with one query instead of two. - 1992 -- Deutsch and Richard Jozsa generalized the algorithm to arbitrary
. - 1993 -- Ethan Bernstein and Umesh Vazirani published a related algorithm (Bernstein-Vazirani), which uses the same circuit structure to solve a different problem.
The Deutsch-Jozsa algorithm is primarily of theoretical interest. It demonstrates the principle of quantum parallelism and phase kickback, which underpin more practical algorithms like Shor's and Grover's.
Solution
Circuit Structure
The Deutsch-Jozsa circuit uses
Step 1: Initialize. Prepare
Step 2: Hadamard all qubits. Apply
Step 3: Apply the oracle
Step 4: Hadamard on input qubits. Apply
Step 5: Measure. Measure the
- If
is constant, then is always or always , so . We measure all zeros with certainty. - If
is balanced, the positive and negative terms cancel exactly, so . We never measure all zeros.
Decision Rule
| Measurement of input qubits | Conclusion |
|---|---|
| All zeros: | |
| Anything else |
Code
The 1-Qubit Deutsch Algorithm
The simplest case (
There are four possible functions:
| Function | Type | ||
|---|---|---|---|
| 0 | 0 | Constant | |
| 1 | 1 | Constant | |
| 0 | 1 | Balanced | |
| 1 | 0 | Balanced |
from pyqpanda3 import core
def deutsch_oracle(oracle_type: str) -> core.QProg:
"""Build a 1-qubit Deutsch oracle for the four possible functions.
The oracle acts on 2 qubits: qubit 0 is the input, qubit 1 is the ancilla.
For a constant-0 oracle, nothing happens (identity).
For a constant-1 oracle, X is applied to the ancilla.
For balanced oracles, CNOT or CNOT+X encodes the function.
Args:
oracle_type: One of 'const0', 'const1', 'balanced01', 'balanced10'.
Returns:
A QProg containing the oracle gates.
"""
oracle = core.QProg()
if oracle_type == "const0":
# f(0)=0, f(1)=0: U_f = identity (no gates needed)
pass
elif oracle_type == "const1":
# f(0)=1, f(1)=1: flip ancilla regardless of input
oracle << core.X(1)
elif oracle_type == "balanced01":
# f(0)=0, f(1)=1: CNOT from input to ancilla
oracle << core.CNOT(0, 1)
elif oracle_type == "balanced10":
# f(0)=1, f(1)=0: CNOT + X on ancilla
oracle << core.CNOT(0, 1)
oracle << core.X(1)
else:
raise ValueError(f"Unknown oracle type: {oracle_type}")
return oracle
def deutsch_algorithm(oracle_type: str) -> str:
"""Run the 1-qubit Deutsch algorithm.
Args:
oracle_type: The oracle function to test.
Returns:
'constant' or 'balanced'.
"""
prog = core.QProg()
# Step 1: Initialize ancilla qubit 1 to |1>.
prog << core.X(1)
# Step 2: Hadamard on both qubits.
prog << core.H(0)
prog << core.H(1)
# Step 3: Apply the oracle.
prog << deutsch_oracle(oracle_type)
# Step 4: Hadamard on the input qubit.
prog << core.H(0)
# Step 5: Measure input qubit.
prog << core.measure([0], [0])
# Run the circuit.
machine = core.CPUQVM()
machine.run(prog, shots=1)
counts = machine.result().get_counts()
# If we measure |0>, f is constant; if |1>, f is balanced.
result_bit = list(counts.keys())[0]
return "constant" if result_bit == "0" else "balanced"
# Test all four possible 1-bit functions.
print("=== 1-Qubit Deutsch Algorithm ===")
for oracle_type in ["const0", "const1", "balanced01", "balanced10"]:
result = deutsch_algorithm(oracle_type)
print(f" {oracle_type:>14s} => {result}")Expected output:
=== 1-Qubit Deutsch Algorithm ===
const0 => constant
const1 => constant
balanced01 => balanced
balanced10 => balancedn-Qubit Deutsch-Jozsa with a Constant Oracle
A constant oracle is trivial to implement: it either does nothing (
from pyqpanda3 import core
def constant_oracle(n: int, value: int) -> core.QProg:
"""Build a constant oracle: f(x) = value for all x.
Args:
n: Number of input qubits.
value: 0 or 1 (the constant output).
Returns:
A QProg implementing the constant oracle.
"""
oracle = core.QProg()
if value == 1:
# Ancilla is qubit n. Flip it to encode f(x)=1.
oracle << core.X(n)
# If value == 0, the oracle is the identity (no gates).
return oracle
def deutsch_jozsa(n: int, oracle: core.QProg) -> str:
"""Run the Deutsch-Jozsa algorithm on n input qubits.
Args:
n: Number of input qubits.
oracle: A QProg implementing the oracle U_f.
Returns:
'constant' or 'balanced'.
"""
prog = core.QProg()
# Step 1: Initialize ancilla (qubit n) to |1>.
prog << core.X(n)
# Step 2: Hadamard on all n+1 qubits.
for i in range(n + 1):
prog << core.H(i)
# Step 3: Apply the oracle.
prog << oracle
# Step 4: Hadamard on the n input qubits.
for i in range(n):
prog << core.H(i)
# Step 5: Measure the n input qubits.
prog << core.measure(list(range(n)), list(range(n)))
# Run the circuit.
machine = core.CPUQVM()
machine.run(prog, shots=1)
counts = machine.result().get_counts()
# If the measurement is all zeros, f is constant; otherwise balanced.
all_zeros = "0" * n
result_key = list(counts.keys())[0]
return "constant" if result_key == all_zeros else "balanced"
# Test with a constant-0 oracle on 3 input qubits.
n = 3
print(f"=== Constant-0 Oracle (n={n}) ===")
oracle = constant_oracle(n, value=0)
result = deutsch_jozsa(n, oracle)
print(f" Result: {result}")
assert result == "constant"
# Test with a constant-1 oracle.
print(f"\n=== Constant-1 Oracle (n={n}) ===")
oracle = constant_oracle(n, value=1)
result = deutsch_jozsa(n, oracle)
print(f" Result: {result}")
assert result == "constant"Expected output:
=== Constant-0 Oracle (n=3) ===
Result: constant
=== Constant-1 Oracle (n=3) ===
Result: constantn-Qubit Deutsch-Jozsa with a Balanced Oracle
A balanced oracle returns
For the parity function
from pyqpanda3 import core
def balanced_parity_oracle(n: int) -> core.QProg:
"""Build a balanced oracle computing the parity of the input.
f(x) = x_0 XOR x_1 XOR ... XOR x_{n-1}
This is balanced: exactly half of all inputs have even parity (f=0)
and half have odd parity (f=1).
Args:
n: Number of input qubits.
Returns:
A QProg implementing the parity oracle.
"""
oracle = core.QProg()
# CNOT from each input qubit to the ancilla (qubit n).
for i in range(n):
oracle << core.CNOT(i, n)
return oracle
# Test the balanced parity oracle on 3 input qubits.
n = 3
print(f"=== Balanced Parity Oracle (n={n}) ===")
oracle = balanced_parity_oracle(n)
result = deutsch_jozsa(n, oracle)
print(f" Result: {result}")
assert result == "balanced"Expected output:
=== Balanced Parity Oracle (n=3) ===
Result: balancedBuilding Balanced Oracles with CNOT and X Gates
There are many balanced functions. This section demonstrates several constructions and verifies that Deutsch-Jozsa correctly identifies all of them as balanced.
from pyqpanda3 import core
def balanced_first_bit_oracle(n: int) -> core.QProg:
"""Balanced oracle: f(x) = x_0 (the first input bit).
Exactly half the inputs have x_0 = 0 and half have x_0 = 1.
"""
oracle = core.QProg()
oracle << core.CNOT(0, n)
return oracle
def balanced_half_oracle(n: int) -> core.QProg:
"""Balanced oracle: f(x) = 1 iff x < 2^{n-1}.
The first half of all inputs (by numerical value) maps to 1,
the second half to 0. Achieved by CNOT on the most significant bit
followed by X on the ancilla.
"""
oracle = core.QProg()
oracle << core.CNOT(n - 1, n)
oracle << core.X(n)
return oracle
def balanced_xor_pair_oracle(n: int) -> core.QProg:
"""Balanced oracle: f(x) = x_0 XOR x_1 (requires n >= 2).
Balanced because for each setting of the other bits, exactly one
of (x_0=0,x_1=1) and (x_0=1,x_1=0) gives f=1.
"""
if n < 2:
raise ValueError("XOR pair oracle requires n >= 2")
oracle = core.QProg()
oracle << core.CNOT(0, n)
oracle << core.CNOT(1, n)
return oracle
# Test multiple balanced oracle constructions.
n = 4
print(f"=== Multiple Balanced Oracles (n={n}) ===")
oracles = {
"parity (all bits)": balanced_parity_oracle(n),
"first bit": balanced_first_bit_oracle(n),
"first half": balanced_half_oracle(n),
"x_0 XOR x_1": balanced_xor_pair_oracle(n),
}
for name, oracle in oracles.items():
result = deutsch_jozsa(n, oracle)
print(f" {name:>20s} => {result}")
assert result == "balanced"Expected output:
=== Multiple Balanced Oracles (n=4) ===
parity (all bits) => balanced
first bit => balanced
first half => balanced
x_0 XOR x_1 => balancedTesting Multiple Oracle Types at Scale
This example runs a comprehensive test: for each
from pyqpanda3 import core
def run_all_tests(max_n: int = 6) -> None:
"""Run Deutsch-Jozsa on all oracle types for n = 1 to max_n.
Tests constant-0, constant-1, and balanced (parity) oracles.
Reports results and asserts correctness.
"""
print(f"{'n':>3s} {'Oracle':<20s} {'Result':<10s} {'Correct?'}")
print("-" * 50)
for n in range(1, max_n + 1):
# Constant-0
result = deutsch_jozsa(n, constant_oracle(n, 0))
correct = result == "constant"
print(f"{n:>3d} {'constant-0':<20s} {result:<10s} {'OK' if correct else 'FAIL'}")
# Constant-1
result = deutsch_jozsa(n, constant_oracle(n, 1))
correct = result == "constant"
print(f"{n:>3d} {'constant-1':<20s} {result:<10s} {'OK' if correct else 'FAIL'}")
# Balanced (parity)
result = deutsch_jozsa(n, balanced_parity_oracle(n))
correct = result == "balanced"
print(f"{n:>3d} {'balanced (parity)':<20s} {result:<10s} {'OK' if correct else 'FAIL'}")
print("\nAll tests passed!")
run_all_tests(max_n=6)Expected output:
n Oracle Result Correct?
--------------------------------------------------
1 constant-0 constant OK
1 constant-1 constant OK
1 balanced (parity) balanced OK
2 constant-0 constant OK
2 constant-1 constant OK
2 balanced (parity) balanced OK
3 constant-0 constant OK
3 constant-1 constant OK
3 balanced (parity) balanced OK
4 constant-0 constant OK
4 constant-1 constant OK
4 balanced (parity) balanced OK
5 constant-0 constant OK
5 constant-1 constant OK
5 balanced (parity) balanced OK
6 constant-0 constant OK
6 constant-1 constant OK
6 balanced (parity) balanced OK
All tests passed!Verifying with State Vector Analysis
To understand what happens internally, run the circuit without measurement and inspect the state vector. This confirms that the constant oracle produces
from pyqpanda3 import core
import numpy as np
def analyze_state_vector(n: int, oracle: core.QProg, label: str) -> None:
"""Run Deutsch-Jozsa without measurement and print the input-qubit state.
Args:
n: Number of input qubits.
oracle: The oracle to apply.
label: Description for display.
"""
prog = core.QProg()
# Initialize ancilla to |1>.
prog << core.X(n)
# Hadamard all qubits.
for i in range(n + 1):
prog << core.H(i)
# Apply oracle.
prog << oracle
# Hadamard on input qubits only.
for i in range(n):
prog << core.H(i)
# No measurement -- get state vector.
machine = core.CPUQVM()
machine.run(prog, shots=1)
sv = machine.result().get_state_vector()
print(f"\n--- {label} (n={n}) ---")
print(f"State vector length: {len(sv)}")
# Extract probabilities for the input qubits.
# The ancilla (qubit n) should be in (|0> - |1>)/sqrt(2) throughout.
# We look at the probability of the all-zero state on input qubits.
all_zeros_idx = 0 # ancilla |0>: input qubits all |0>
# Also index where input qubits all 0 and ancilla is |1>
all_zeros_with_anc1 = 2 ** n
amp_0 = sv[all_zeros_idx]
amp_1 = sv[all_zeros_with_anc1]
prob_input_zeros = abs(amp_0) ** 2 + abs(amp_1) ** 2
print(f"P(input qubits = |0>^{n}) = {prob_input_zeros:.6f}")
print(f" Amplitude |0>^{n}|0>_anc: {amp_0:.6f}")
print(f" Amplitude |0>^{n}|1>_anc: {amp_1:.6f}")
# Print all non-zero amplitudes for inspection.
print(f"\nNon-zero amplitudes (|threshold| > 0.001):")
for idx in range(len(sv)):
if abs(sv[idx]) > 0.001:
bits = format(idx, f"0{n + 1}b")
print(f" |{bits}>: {sv[idx]:.6f}")
# Analyze constant-0 oracle.
n = 3
analyze_state_vector(n, constant_oracle(n, 0), "Constant-0 Oracle")
# Analyze balanced parity oracle.
analyze_state_vector(n, balanced_parity_oracle(n), "Balanced Parity Oracle")Expected output:
--- Constant-0 Oracle (n=3) ---
State vector length: 16
P(input qubits = |0>^3) = 1.000000
Amplitude |0>^3|0>_anc: 0.707107
Amplitude |0>^3|1>_anc: -0.707107
Non-zero amplitudes (|threshold| > 0.001):
|0000>: 0.707107
|0001>: -0.707107
--- Balanced Parity Oracle (n=3) ---
State vector length: 16
P(input qubits = |0>^3) = 0.000000
Amplitude |0>^3|0>_anc: 0.000000
Amplitude |0>^3|1>_anc: 0.000000
Non-zero amplitudes (|threshold| > 0.001):
|0010>: 0.500000
|0100>: 0.500000
|1000>: -0.500000
|1110>: -0.500000
|0011>: -0.500000
|0101>: -0.500000
|1010>: -0.500000
|1111>: -0.500000For the constant oracle, all amplitude is concentrated in
Running on the Stabilizer Simulator
The Deutsch-Jozsa circuit uses only Clifford gates (H, X, CNOT), so it can be simulated efficiently using the Stabilizer backend. This is useful for large
from pyqpanda3 import core
def deutsch_jozsa_stabilizer(n: int, oracle: core.QProg) -> str:
"""Run Deutsch-Jozsa using the Stabilizer simulator.
Args:
n: Number of input qubits.
oracle: The oracle to apply.
Returns:
'constant' or 'balanced'.
"""
prog = core.QProg()
prog << core.X(n)
for i in range(n + 1):
prog << core.H(i)
prog << oracle
for i in range(n):
prog << core.H(i)
prog << core.measure(list(range(n)), list(range(n)))
stab = core.Stabilizer()
stab.run(prog, shots=1)
counts = stab.result().get_counts()
all_zeros = "0" * n
result_key = list(counts.keys())[0]
return "constant" if result_key == all_zeros else "balanced"
# Verify the Stabilizer gives the same results as CPUQVM.
n = 5
print(f"=== Stabilizer Backend (n={n}) ===")
result = deutsch_jozsa_stabilizer(n, constant_oracle(n, 0))
print(f" Constant-0: {result}")
result = deutsch_jozsa_stabilizer(n, balanced_parity_oracle(n))
print(f" Balanced: {result}")Expected output:
=== Stabilizer Backend (n=5) ===
Constant-0: constant
Balanced: balancedDeutsch-Jozsa with Noise Simulation
Real quantum hardware introduces errors. This example shows how depolarizing noise can cause the Deutsch-Jozsa algorithm to produce incorrect results for the constant case (spurious non-zero measurement outcomes).
from pyqpanda3 import core
def deutsch_jozsa_noisy(n: int, oracle: core.QProg, shots: int = 1000) -> None:
"""Run Deutsch-Jozsa with a depolarizing noise model.
Prints the measurement distribution and inferred result.
"""
prog = core.QProg()
prog << core.X(n)
for i in range(n + 1):
prog << core.H(i)
prog << oracle
for i in range(n):
prog << core.H(i)
prog << core.measure(list(range(n)), list(range(n)))
# Noise model: 1% depolarizing on single-qubit gates, 2% on CNOT.
noise = core.NoiseModel()
for i in range(n + 1):
noise.add_quantum_error(
core.depolarizing_error(0.01), core.GateType.H, [i]
)
for i in range(n):
noise.add_quantum_error(
core.depolarizing_error(0.02), core.GateType.CNOT, [i, n]
)
machine = core.CPUQVM()
machine.run(prog, shots=shots, model=noise)
counts = machine.result().get_counts()
all_zeros = "0" * n
constant_prob = counts.get(all_zeros, 0) / shots
print(f" P(|{all_zeros}>) = {constant_prob:.4f}")
print(f" Counts: {counts}")
# With noise, the result is a majority vote.
if constant_prob > 0.5:
print(f" => Likely CONSTANT (noise-affected)")
else:
print(f" => Likely BALANCED")
n = 3
print(f"=== Noisy Deutsch-Jozsa (n={n}, 1% H / 2% CNOT depolarizing) ===")
print(f"\nConstant-0 oracle:")
deutsch_jozsa_noisy(n, constant_oracle(n, 0))
print(f"\nBalanced parity oracle:")
deutsch_jozsa_noisy(n, balanced_parity_oracle(n))Expected output (approximate):
=== Noisy Deutsch-Jozsa (n=3, 1% H / 2% CNOT depolarizing) ===
Constant-0 oracle:
P(|000>) = 0.9430
Counts: {'000': 943, '001': 12, '010': 15, '100': 10, '110': 8, '011': 4, '101': 5, '111': 3}
=> Likely CONSTANT (noise-affected)
Balanced parity oracle:
P(|000>) = 0.0020
Counts: {'010': 198, '011': 203, '001': 195, '101': 104, '110': 100, '100': 100, '111': 98}
=> Likely BALANCEDThe constant oracle retains a high probability on
Explanation
Phase Kickback
The key mechanism behind Deutsch-Jozsa is phase kickback. The oracle
where
Now consider what happens when the ancilla is in the state
If
If
In both cases, the ancilla remains in
This is the phase kickback: the function value is "kicked back" from the ancilla into the phase of the input register. The ancilla is effectively a catalyst -- it enables the phase encoding without being changed.
Why the Ancilla Starts in |1>
The ancilla must be prepared in
If the ancilla started in
Both
Hadamard Transform Analysis
The
where
The full Deutsch-Jozsa algorithm applies
The amplitude of
Constant case:
Balanced case: The sum splits into
The constructive/destructive interference is total and deterministic -- there is no probabilistic ambiguity.
Step-by-Step Trace for n = 2
Let us trace through the algorithm for
Constant-0 Oracle:
| Step | State of input qubits | State of ancilla |
|---|---|---|
| Initialize | ||
| After | ||
| After | ||
| After | ||
| Measure | -- |
Since
Balanced Oracle:
| Step | State of input qubits | State of ancilla |
|---|---|---|
| Initialize | ||
| After | ||
| After | ||
| After | ||
| Measure | -- |
The phases from
The Oracle as a Quantum Gate
The oracle
This is always unitary because XOR is its own inverse: applying
For specific functions,
| Function | Oracle Circuit | Gate Count |
|---|---|---|
| Identity | 0 | |
| X on ancilla | 1 | |
| CNOT from qubit | 1 | |
| CNOT( | 2 | |
| CNOT from each input to ancilla | ||
| CNOT(0, anc) + X(anc) | 2 |
Any balanced function can be expressed as
Connection to Bernstein-Vazirani
The Deutsch-Jozsa circuit is structurally identical to the Bernstein-Vazirani algorithm. While Deutsch-Jozsa distinguishes constant from balanced, Bernstein-Vazirani solves a different problem:
Bernstein-Vazirani problem. Given oracle access to
The same circuit (Hadamard, oracle, Hadamard, measure) produces exactly
from pyqpanda3 import core
def bernstein_vazirani(n: int, secret: str) -> str:
"""Run the Bernstein-Vazirani algorithm to recover a secret string.
Args:
n: Number of input qubits.
secret: Binary string of length n (e.g., '101').
Returns:
The recovered secret string.
"""
prog = core.QProg()
# Initialize ancilla to |1>.
prog << core.X(n)
# Hadamard all qubits.
for i in range(n + 1):
prog << core.H(i)
# Oracle: f_s(x) = s . x.
# CNOT from qubit i to ancilla for each s_i = 1.
for i in range(n):
if secret[i] == "1":
prog << core.CNOT(i, n)
# Hadamard on input qubits.
for i in range(n):
prog << core.H(i)
# Measure input qubits.
prog << core.measure(list(range(n)), list(range(n)))
# Run and return result.
machine = core.CPUQVM()
machine.run(prog, shots=1)
counts = machine.result().get_counts()
return list(counts.keys())[0]
# Test Bernstein-Vazirani.
n = 4
secrets = ["0000", "0001", "1010", "1111", "0101"]
print("=== Bernstein-Vazirani ===")
for s in secrets:
recovered = bernstein_vazirani(n, s)
print(f" Secret: {s} => Recovered: {recovered} {'OK' if recovered == s else 'FAIL'}")Expected output:
=== Bernstein-Vazirani ===
Secret: 0000 => Recovered: 0000 OK
Secret: 0001 => Recovered: 0001 OK
Secret: 1010 => Recovered: 1010 OK
Secret: 1111 => Recovered: 1111 OK
Secret: 0101 => Recovered: 0101 OKNote that the secret
Connection to Other Quantum Algorithms
The techniques introduced by Deutsch-Jozsa appear throughout quantum computing:
- Phase kickback is the same mechanism used in quantum phase estimation, which underlies Shor's factoring algorithm.
- Hadamard transforms for creating superpositions and performing interference are used in virtually every quantum algorithm.
- Oracle-based algorithms (Grover, Simon) follow the same pattern of querying a black-box function in superposition.
- Constructive/destructive interference to amplify the correct answer appears in Grover's amplitude amplification.
Limitations and Practical Considerations
Probabilistic classical algorithm. Although the worst-case classical complexity is
Oracle construction cost. In practice, someone must build the oracle circuit. If the oracle requires
Deterministic quantum advantage. Unlike the randomized classical algorithm, the quantum algorithm gives the correct answer with probability 1 (no error). This is rare among quantum algorithms -- most (like Grover's) have a small error probability.
Noise sensitivity. On noisy hardware, the constant oracle may produce non-zero measurement outcomes, leading to a false "balanced" verdict. Error mitigation or majority voting over multiple shots is needed.
Summary of Resources
| Resource | Deutsch-Jozsa |
|---|---|
| Qubits | |
| Oracle queries | 1 |
| Hadamard gates | |
| Total gates (parity oracle) | |
| Circuit depth | |
| Measurement shots | 1 (deterministic) |
The algorithm is deterministic (probability 1 of correctness), requires only a single shot, and uses a linear number of gates.