"""
Deutsch-Josza Algorithm is one of the first examples of a quantum
algorithm that is exponentially faster than any possible deterministic
classical algorithm
Premise:
We are given a hidden Boolean function f,
which takes as input a string of bits, and returns either 0 or 1:
f({x0,x1,x2,...}) -> 0 or 1, where xn is 0 or 1
The property of the given Boolean function is that it is guaranteed to
either be balanced or constant. A constant function returns all 0's
or all 1's for any input, while a balanced function returns 0's for
exactly half of all inputs and 1's for the other half. Our task is to
determine whether the given function is balanced or constant.
References:
- https://en.wikipedia.org/wiki/Deutsch-Jozsa_algorithm
- https://qiskit.org/textbook/ch-algorithms/deutsch-jozsa.html
"""
import numpy as np
import qiskit as q
def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit:
"""
Returns a Quantum Circuit for the Oracle function.
The circuit returned can represent balanced or constant function,
according to the arguments passed
"""
oracle_qc = q.QuantumCircuit(num_qubits + 1)
if case == "balanced":
b = np.random.randint(1, 2**num_qubits)
b_str = format(b, f"0{num_qubits}b")
for index, bit in enumerate(b_str):
if bit == "1":
oracle_qc.x(index)
for index in range(num_qubits):
oracle_qc.cx(index, num_qubits)
for index, bit in enumerate(b_str):
if bit == "1":
oracle_qc.x(index)
if case == "constant":
output = np.random.randint(2)
if output == 1:
oracle_qc.x(num_qubits)
oracle_gate = oracle_qc.to_gate()
oracle_gate.name = "Oracle"
return oracle_gate
def dj_algorithm(oracle: q.QuantumCircuit, num_qubits: int) -> q.QuantumCircuit:
"""
Returns the complete Deustch-Jozsa Quantum Circuit,
adding Input & Output registers and Hadamard & Measurement Gates,
to the Oracle Circuit passed in arguments
"""
dj_circuit = q.QuantumCircuit(num_qubits + 1, num_qubits)
dj_circuit.x(num_qubits)
dj_circuit.h(num_qubits)
for qubit in range(num_qubits):
dj_circuit.h(qubit)
dj_circuit.append(oracle, range(num_qubits + 1))
for qubit in range(num_qubits):
dj_circuit.h(qubit)
for i in range(num_qubits):
dj_circuit.measure(i, i)
return dj_circuit
def deutsch_jozsa(case: str, num_qubits: int) -> q.result.counts.Counts:
"""
Main function that builds the circuit using other helper functions,
runs the experiment 1000 times & returns the resultant qubit counts
>>> deutsch_jozsa("constant", 3)
{'000': 1000}
>>> deutsch_jozsa("balanced", 3)
{'111': 1000}
"""
simulator = q.Aer.get_backend("qasm_simulator")
oracle_gate = dj_oracle(case, num_qubits)
dj_circuit = dj_algorithm(oracle_gate, num_qubits)
job = q.execute(dj_circuit, simulator, shots=1000)
return job.result().get_counts(dj_circuit)
if __name__ == "__main__":
print(f"Deutsch Jozsa - Constant Oracle: {deutsch_jozsa('constant', 3)}")
print(f"Deutsch Jozsa - Balanced Oracle: {deutsch_jozsa('balanced', 3)}")