18 — Classical Functions on Quantum Hardware
Boolean oracles, phase oracles, and the uncomputation trick
Why This Matters

Quantum algorithms don't operate in a vacuum — they solve problems about classical functions. Shor's algorithm factors numbers. Grover's algorithm searches databases. Deutsch-Jozsa determines whether a function is balanced or constant. In every case, the classical function \(f\) must be embedded into a quantum circuit that can process it in superposition.

But you can't just "run \(f\)" on a quantum computer. Quantum gates must be reversible (unitary), and most classical functions are not. The solution: encode \(f\) as a reversible quantum oracle and use clever tricks (phase kickback, uncomputation) to extract exactly the information you need.

The punchline

There are two standard oracle types. The Boolean oracle \(U_f\ket{x}\ket{y} = \ket{x}\ket{y \oplus f(x)}\) stores \(f(x)\) in an ancilla register. The phase oracle \(P_f\ket{x} = (-1)^{f(x)}\ket{x}\) hides \(f(x)\) in the phase. The phase oracle is built from the Boolean oracle using phase kickback — one of the most important techniques in quantum computing.

The Problem: Reversibility

Recall from W2: quantum gates must be unitary, which means reversible. Given the output, you can always recover the input. But most classical functions are not reversible:

Why you can't just "quantize" a classical function

Consider \(f(x) = x \bmod 2\) (parity function). This maps \(f(0) = 0\), \(f(1) = 1\), \(f(2) = 0\), \(f(3) = 1\). Given output 0, was the input 0 or 2? You can't tell. The function is not injective, so it's not reversible.

If you tried to build a unitary \(U\) that maps \(\ket{x} \to \ket{f(x)}\), you'd need \(\ket{0} \to \ket{0}\) and \(\ket{2} \to \ket{0}\). Two different inputs map to the same output. This violates unitarity — unitary matrices must be invertible.

More concretely: if \(U\ket{0} = \ket{0}\) and \(U\ket{2} = \ket{0}\), then \(\braket{0|2} = 0\) but \(\bra{0}U^\dagger U\ket{2} = \braket{0|0} = 1\). Since \(U^\dagger U\) should equal \(I\), we need \(\bra{0}U^\dagger U\ket{2} = \braket{0|2} = 0\). Contradiction.

The Boolean Oracle

The solution is to add an output register (ancilla qubits). Instead of overwriting the input with \(f(x)\), store the result separately using XOR:

Boolean oracle definition

For a classical function \(f: \{0,1\}^n \to \{0,1\}^m\), the Boolean oracle is the unitary:

$$U_f\ket{x}\ket{y} = \ket{x}\ket{y \oplus f(x)}$$

where \(\ket{x}\) is the \(n\)-qubit input register, \(\ket{y}\) is the \(m\)-qubit output register, and \(\oplus\) is bitwise XOR.

Why this is reversible

Apply \(U_f\) twice: \(U_f U_f\ket{x}\ket{y} = U_f\ket{x}\ket{y \oplus f(x)} = \ket{x}\ket{y \oplus f(x) \oplus f(x)} = \ket{x}\ket{y}\). Since \(a \oplus a = 0\) for any bitstring, \(U_f\) is its own inverse. It's a permutation of basis states — always unitary.

The key insight: the input is preserved. Because \(\ket{x}\) is not modified, no information is lost, and different inputs always produce different outputs (in the joint input-output space).

Worked Example 1: Boolean oracle for \(f(x) = x \bmod 2\)

Let \(f: \{0,1\}^2 \to \{0,1\}\) with \(f(00) = 0, f(01) = 1, f(10) = 0, f(11) = 1\). The oracle acts on 3 qubits (2 input + 1 output):

$$U_f\ket{00}\ket{0} = \ket{00}\ket{0 \oplus 0} = \ket{00}\ket{0} \qquad U_f\ket{01}\ket{0} = \ket{01}\ket{0 \oplus 1} = \ket{01}\ket{1}$$

$$U_f\ket{10}\ket{0} = \ket{10}\ket{0 \oplus 0} = \ket{10}\ket{0} \qquad U_f\ket{11}\ket{0} = \ket{11}\ket{0 \oplus 1} = \ket{11}\ket{1}$$

When the output register starts at \(\ket{0}\), the XOR just writes \(f(x)\) into it: \(\ket{0 \oplus f(x)} = \ket{f(x)}\). This is the standard usage — initialize the output register to \(\ket{0}\) to "query" the function.

Worked Example 2: Querying in superposition

The power of the Boolean oracle is that it works on superpositions. Apply \(U_f\) to \(\ket{+}\ket{0} = \frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\ket{0}\) for \(f: \{0,1\} \to \{0,1\}\) with \(f(0) = 0, f(1) = 1\):

$$U_f\left(\frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\ket{0}\right) = \frac{1}{\sqrt{2}}(\ket{0}\ket{f(0)} + \ket{1}\ket{f(1)}) = \frac{1}{\sqrt{2}}(\ket{0}\ket{0} + \ket{1}\ket{1})$$

The output is \(\ket{\Phi^+}\) — a Bell state! A single oracle query evaluated \(f\) on both inputs simultaneously. This is quantum parallelism.

But you can't just read both answers

The result is an entangled state. If you measure, you get ONE random outcome (\(\ket{00}\) or \(\ket{11}\)). You don't get to see both \(f(0)\) and \(f(1)\). The art of quantum algorithms is arranging interference so that the measurement outcome reveals the global property you care about (constant vs balanced, period, etc.) without needing to read individual values.

The Phase Oracle

Many quantum algorithms (Deutsch-Jozsa, Grover's, Simon's) use a different oracle that encodes \(f(x)\) in the phase rather than in an ancilla register:

Phase oracle definition

For \(f: \{0,1\}^n \to \{0,1\}\) (single-bit output), the phase oracle is:

$$P_f\ket{x} = (-1)^{f(x)}\ket{x}$$

If \(f(x) = 0\), the state is unchanged. If \(f(x) = 1\), the state picks up a minus sign. No ancilla register needed — the information is hidden in the phase.

Why is this useful? A single phase (\(+1\) or \(-1\)) on one basis state is unobservable — you can't measure phase directly. But when \(\ket{x}\) is in superposition, different terms get stamped with different phases:

$$\frac{1}{\sqrt{2}}\left((-1)^{f(0)}\ket{0} + (-1)^{f(1)}\ket{1}\right)$$

Now apply \(H\). The Hadamard converts phase patterns into measurable states:

Measure one bit: 0 = constant, 1 = balanced. You learned a global property of \(f\) without ever knowing \(f(0)\) or \(f(1)\) individually. The phase was the carrier, \(H\) was the decoder, measurement was the readout. This is the core mechanism behind most quantum speedups.

Building the phase oracle from the Boolean oracle

You don't need to construct \(P_f\) from scratch. There's a universal trick using the Boolean oracle and phase kickback:

Phase kickback construction

Initialize the output register to \(\ket{-} = \frac{1}{\sqrt{2}}(\ket{0} - \ket{1})\) instead of \(\ket{0}\):

$$U_f\ket{x}\ket{-} = (-1)^{f(x)}\ket{x}\ket{-}$$

The output register is unchanged (it stays \(\ket{-}\)). The effect of \(f(x)\) has been "kicked back" into the phase of \(\ket{x}\). Since the ancilla ends in the same state it started in, we can ignore it — effectively implementing \(P_f\) on the input register.

Worked Example 3: Phase kickback derivation

Let's trace through the algebra carefully. Starting with \(\ket{x}\ket{-}\) where \(\ket{-} = \frac{1}{\sqrt{2}}(\ket{0} - \ket{1})\):

$$U_f\ket{x}\ket{-} = U_f\ket{x}\frac{1}{\sqrt{2}}(\ket{0} - \ket{1})$$

$$= \frac{1}{\sqrt{2}}\left(U_f\ket{x}\ket{0} - U_f\ket{x}\ket{1}\right) = \frac{1}{\sqrt{2}}\left(\ket{x}\ket{0 \oplus f(x)} - \ket{x}\ket{1 \oplus f(x)}\right)$$

Now consider two cases:

Case \(f(x) = 0\): \(\frac{1}{\sqrt{2}}\left(\ket{x}\ket{0} - \ket{x}\ket{1}\right) = \ket{x}\ket{-} = (-1)^0\ket{x}\ket{-}\)

Case \(f(x) = 1\): \(\frac{1}{\sqrt{2}}\left(\ket{x}\ket{1} - \ket{x}\ket{0}\right) = -\ket{x}\ket{-} = (-1)^1\ket{x}\ket{-}\)

In both cases: \(U_f\ket{x}\ket{-} = (-1)^{f(x)}\ket{x}\ket{-}\). The \(\ket{-}\) ancilla acted as a "phase detector" — the XOR with \(f(x)\) flipped \(\ket{0} \leftrightarrow \ket{1}\) inside the minus superposition, which is equivalent to multiplying by \(-1\).

Worked Example 4: Phase oracle on a superposition

Apply \(P_f\) to \(\ket{+} = \frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\), for \(f(0) = 0, f(1) = 1\) (the identity function):

$$P_f\ket{+} = \frac{1}{\sqrt{2}}\left((-1)^{f(0)}\ket{0} + (-1)^{f(1)}\ket{1}\right) = \frac{1}{\sqrt{2}}(\ket{0} - \ket{1}) = \ket{-}$$

The phase oracle turned \(\ket{+}\) into \(\ket{-}\). Now apply H: \(H\ket{-} = \ket{1}\). We can measure and deterministically learn something about \(f\) — this is the core of the Deutsch algorithm.

Garbage and Uncomputation

Real implementations of \(U_f\) often produce garbage — extra information in ancilla qubits that's needed during computation but shouldn't persist in the output.

The garbage problem

Suppose computing \(f(x)\) requires intermediate results stored in ancilla qubits. The actual operation might be:

$$V_f\ket{x}\ket{0}\ket{0} = \ket{x}\ket{f(x)}\ket{g(x)}$$

where \(\ket{g(x)}\) is "garbage" — intermediate computation results that we don't want. This garbage is problematic because it's entangled with the input, preventing interference.

Why does garbage prevent interference? Consider querying \(V_f\) in superposition:

$$V_f\frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\ket{0}\ket{0} = \frac{1}{\sqrt{2}}(\ket{0}\ket{f(0)}\ket{g(0)} + \ket{1}\ket{f(1)}\ket{g(1)})$$

If \(g(0) \neq g(1)\), the two branches are in different states of the garbage register, so they can't interfere. Even if \(f(0) = f(1)\), the garbage makes the branches orthogonal. This destroys the quantum speedup.

The uncomputation trick

The fix is elegant: copy the answer out, then run the computation backwards to clean up the garbage.

Uncomputation protocol
  1. Compute: \(V_f\ket{x}\ket{0}_{\text{out}}\ket{0}_{\text{garb}} = \ket{x}\ket{f(x)}\ket{g(x)}\)
  2. Copy: CNOT the result register into a fresh output register: \(\ket{x}\ket{f(x)}\ket{g(x)}\ket{0} \to \ket{x}\ket{f(x)}\ket{g(x)}\ket{f(x)}\)
  3. Uncompute: Apply \(V_f^\dagger\) to reverse step 1: \(\ket{x}\ket{f(x)}\ket{g(x)}\ket{f(x)} \to \ket{x}\ket{0}\ket{0}\ket{f(x)}\)

Final state: \(\ket{x}\ket{0}\ket{0}\ket{f(x)}\). The garbage qubits are back to \(\ket{0}\) — they're no longer entangled with anything. The clean output register holds \(f(x)\).

Worked Example 5: Why uncomputation restores interference

After uncomputation, a superposition query gives:

$$\frac{1}{\sqrt{2}}(\ket{0}\ket{0}\ket{0}\ket{f(0)} + \ket{1}\ket{0}\ket{0}\ket{f(1)})$$

The garbage registers are \(\ket{0}\ket{0}\) in both branches — identical. If \(f(0) = f(1)\), the two branches can now interfere constructively or destructively. The garbage is gone, and quantum parallelism works as intended.

Uncomputation as a general principle

Uncomputation is not specific to oracles. It's a general technique in quantum computing: any reversible computation can be reversed by applying its adjoint \(U^\dagger\). Whenever you need intermediate results temporarily, compute them, use them, then uncompute to clean up. This is how quantum computers manage ancilla qubits without accumulating garbage.

Summary
What you need to know