2020-10-18 14:54:46 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
2022-10-19 20:12:44 +00:00
|
|
|
Deutsch-Jozsa Algorithm is one of the first examples of a quantum
|
2020-10-18 14:54:46 +00:00
|
|
|
algorithm that is exponentially faster than any possible deterministic
|
|
|
|
classical algorithm
|
|
|
|
|
|
|
|
Premise:
|
2020-10-18 16:07:27 +00:00
|
|
|
We are given a hidden Boolean function f,
|
2020-10-18 14:54:46 +00:00
|
|
|
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
|
2020-10-18 16:07:27 +00:00
|
|
|
|
2020-10-18 14:54:46 +00:00
|
|
|
The property of the given Boolean function is that it is guaranteed to
|
2020-10-18 16:07:27 +00:00
|
|
|
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
|
2020-10-18 14:54:46 +00:00
|
|
|
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
|
2022-10-19 20:12:44 +00:00
|
|
|
import qiskit
|
2020-10-18 14:54:46 +00:00
|
|
|
|
|
|
|
|
2022-10-19 20:12:44 +00:00
|
|
|
def dj_oracle(case: str, num_qubits: int) -> qiskit.QuantumCircuit:
|
2020-10-18 14:54:46 +00:00
|
|
|
"""
|
|
|
|
Returns a Quantum Circuit for the Oracle function.
|
|
|
|
The circuit returned can represent balanced or constant function,
|
|
|
|
according to the arguments passed
|
|
|
|
"""
|
|
|
|
# This circuit has num_qubits+1 qubits: the size of the input,
|
|
|
|
# plus one output qubit
|
2022-10-19 20:12:44 +00:00
|
|
|
oracle_qc = qiskit.QuantumCircuit(num_qubits + 1)
|
2020-10-18 14:54:46 +00:00
|
|
|
|
|
|
|
# First, let's deal with the case in which oracle is balanced
|
|
|
|
if case == "balanced":
|
|
|
|
# First generate a random number that tells us which CNOTs to
|
|
|
|
# wrap in X-gates:
|
2022-01-30 19:29:54 +00:00
|
|
|
b = np.random.randint(1, 2**num_qubits)
|
2020-10-18 14:54:46 +00:00
|
|
|
# Next, format 'b' as a binary string of length 'n', padded with zeros:
|
|
|
|
b_str = format(b, f"0{num_qubits}b")
|
|
|
|
# Next, we place the first X-gates. Each digit in our binary string
|
2022-10-19 20:12:44 +00:00
|
|
|
# corresponds to a qubit, if the digit is 0, we do nothing, if it's 1
|
2020-10-18 14:54:46 +00:00
|
|
|
# we apply an X-gate to that qubit:
|
|
|
|
for index, bit in enumerate(b_str):
|
|
|
|
if bit == "1":
|
|
|
|
oracle_qc.x(index)
|
|
|
|
# Do the controlled-NOT gates for each qubit, using the output qubit
|
|
|
|
# as the target:
|
|
|
|
for index in range(num_qubits):
|
|
|
|
oracle_qc.cx(index, num_qubits)
|
|
|
|
# Next, place the final X-gates
|
|
|
|
for index, bit in enumerate(b_str):
|
|
|
|
if bit == "1":
|
|
|
|
oracle_qc.x(index)
|
|
|
|
|
|
|
|
# Case in which oracle is constant
|
|
|
|
if case == "constant":
|
|
|
|
# First decide what the fixed output of the oracle will be
|
|
|
|
# (either always 0 or always 1)
|
|
|
|
output = np.random.randint(2)
|
|
|
|
if output == 1:
|
|
|
|
oracle_qc.x(num_qubits)
|
|
|
|
|
|
|
|
oracle_gate = oracle_qc.to_gate()
|
|
|
|
oracle_gate.name = "Oracle" # To show when we display the circuit
|
|
|
|
return oracle_gate
|
|
|
|
|
|
|
|
|
2022-10-19 20:12:44 +00:00
|
|
|
def dj_algorithm(
|
|
|
|
oracle: qiskit.QuantumCircuit, num_qubits: int
|
|
|
|
) -> qiskit.QuantumCircuit:
|
2020-10-18 14:54:46 +00:00
|
|
|
"""
|
2022-10-19 20:12:44 +00:00
|
|
|
Returns the complete Deutsch-Jozsa Quantum Circuit,
|
2020-10-18 14:54:46 +00:00
|
|
|
adding Input & Output registers and Hadamard & Measurement Gates,
|
|
|
|
to the Oracle Circuit passed in arguments
|
|
|
|
"""
|
2022-10-19 20:12:44 +00:00
|
|
|
dj_circuit = qiskit.QuantumCircuit(num_qubits + 1, num_qubits)
|
2020-10-18 14:54:46 +00:00
|
|
|
# Set up the output qubit:
|
|
|
|
dj_circuit.x(num_qubits)
|
|
|
|
dj_circuit.h(num_qubits)
|
|
|
|
# And set up the input register:
|
|
|
|
for qubit in range(num_qubits):
|
|
|
|
dj_circuit.h(qubit)
|
|
|
|
# Let's append the oracle gate to our circuit:
|
|
|
|
dj_circuit.append(oracle, range(num_qubits + 1))
|
|
|
|
# Finally, perform the H-gates again and measure:
|
|
|
|
for qubit in range(num_qubits):
|
|
|
|
dj_circuit.h(qubit)
|
|
|
|
|
|
|
|
for i in range(num_qubits):
|
|
|
|
dj_circuit.measure(i, i)
|
|
|
|
|
|
|
|
return dj_circuit
|
|
|
|
|
|
|
|
|
2022-10-19 20:12:44 +00:00
|
|
|
def deutsch_jozsa(case: str, num_qubits: int) -> qiskit.result.counts.Counts:
|
2020-10-18 14:54:46 +00:00
|
|
|
"""
|
|
|
|
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}
|
|
|
|
"""
|
2022-10-19 20:12:44 +00:00
|
|
|
# Use Aer's simulator
|
|
|
|
simulator = qiskit.Aer.get_backend("aer_simulator")
|
2020-10-18 14:54:46 +00:00
|
|
|
|
|
|
|
oracle_gate = dj_oracle(case, num_qubits)
|
|
|
|
dj_circuit = dj_algorithm(oracle_gate, num_qubits)
|
|
|
|
|
2022-10-19 20:12:44 +00:00
|
|
|
# Execute the circuit on the simulator
|
|
|
|
job = qiskit.execute(dj_circuit, simulator, shots=1000)
|
2020-10-18 14:54:46 +00:00
|
|
|
|
|
|
|
# Return the histogram data of the results of the experiment.
|
|
|
|
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)}")
|