Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Grover's Algorithm using Lightning-Qubit's C++ API #980

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0337eb3
Get build system working to compile against C++ API
jzaia18 Nov 1, 2024
1a78bf0
Fully working implementation of Grover's
jzaia18 Nov 5, 2024
906974e
Introduce extra large 3rd oracle
jzaia18 Nov 5, 2024
d8e57f7
Merge branch 'PennyLaneAI:master' into master
jzaia18 Nov 5, 2024
48dea6c
Implement identical algo to C++ impl with python+pennylane
jzaia18 Nov 5, 2024
3b54a2a
Add benchmarking code to both C++ and python impls
jzaia18 Nov 6, 2024
5203b82
Merge branch 'master' into master
AmintorDusko Nov 7, 2024
1d658fb
Change result extraction to use functional programming style
jzaia18 Nov 7, 2024
5057894
Improve application of oracles by condensing into 1 function
jzaia18 Nov 7, 2024
8875449
Merge branch 'master' of github.com:jzaia18/pennylane-lightning
jzaia18 Nov 7, 2024
aa94a18
Add custom directory to format target, and format files
jzaia18 Nov 8, 2024
915ff6d
Merge branch 'master' into master
AmintorDusko Nov 8, 2024
e466e97
Fix comment rot on oracle function
jzaia18 Nov 8, 2024
65a3eef
Merge branch 'master' of github.com:jzaia18/pennylane-lightning
jzaia18 Nov 8, 2024
8bd1f93
Fix type mismatch caused by attempt to unpack tensor as a numpy object
jzaia18 Nov 8, 2024
0066b3b
Change loop over grover repetitions to forward iterations
jzaia18 Nov 8, 2024
ff86a5e
Refactor macros for oracle definitions into global consts
jzaia18 Nov 8, 2024
ca6634c
Condense creation of results vector into a single STL function call, …
jzaia18 Nov 8, 2024
a5ecaa0
Formatting changes to Python impl to please code-coverage plugin
jzaia18 Nov 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

# All CMakeLists.txt in subdirectories use pennylane_lightning_compile_options and pennylane_lightning_external_libs
add_subdirectory(pennylane_lightning/core/src)
add_subdirectory(jzaia_files)

#####################################################
# Maintain for dependent external package development
Expand Down
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,20 @@ else
cmake --build ./BuildTests $(VERBOSE) --target test
endif

######## Added target:
# rm -rf ./BuildTests
jzaia-demo:
rm -rf ./BuildTests/jzaia_files ./BuildTests/lq_grover ./BuildTests/lq_grover.dir
cmake -BBuildTests -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DENABLE_WARNINGS=ON \
-DPL_BACKEND=$(PL_BACKEND) \
$(OPTIONS)
cmake --build ./BuildTests $(VERBOSE)
./BuildTests/lq_grover
######## End added target

test-cpp-mpi:
rm -rf ./BuildTests
cmake -BBuildTests -G Ninja \
Expand All @@ -152,10 +166,11 @@ format: format-cpp format-python

format-cpp:
./bin/format $(CHECK) ./pennylane_lightning
./bin/format $(CHECK) ./jzaia_files

format-python:
isort --py 312 --profile black -l 100 -p pennylane_lightning ./pennylane_lightning ./mpitests ./tests ./scripts $(ICHECK) $(VERBOSE)
black -t py310 -t py311 -t py312 -l 100 ./pennylane_lightning ./mpitests ./tests ./scripts $(CHECK) $(VERBOSE)
isort --py 312 --profile black -l 100 -p pennylane_lightning ./pennylane_lightning ./mpitests ./tests ./scripts ./jzaia_files $(ICHECK) $(VERBOSE)
black -t py310 -t py311 -t py312 -l 100 ./pennylane_lightning ./mpitests ./tests ./scripts ./jzaia_files $(CHECK) $(VERBOSE)

.PHONY: check-tidy
check-tidy:
Expand Down
14 changes: 14 additions & 0 deletions jzaia_files/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
project(lightning_demo LANGUAGES CXX)
add_executable(lq_grover main.cpp)
target_include_directories(lq_grover PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(lq_grover lightning_qubit
lightning_qubit_observables
lightning_qubit_measurements
lightning_qubit_gates
lightning_gates
lq_gates_kernel_map
lq_gates_register_kernels_x64
)

# Unsure if this is the best way to include the necessary gate implementations
target_include_directories(lq_grover PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../pennylane_lightning/core/src/simulators/lightning_qubit/gates/cpu_kernels/)
143 changes: 143 additions & 0 deletions jzaia_files/grover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
r"""
This module contains a custom implementation of Grover's algorithm. It uses lightning-qubit and is
intended to be used for benchmarking against a C++ implementation directly against the lightning-qubit C++ API
"""

import numpy as np
import pennylane as qml

# Define oracles
ORACLE1_QUBITS = 6
ORACLE1_EXPECTED = [1, 1, 0, 1, 0]

ORACLE2_QUBITS = 10
ORACLE2_EXPECTED = [1, 0, 1, 0, 1, 0, 1, 0, 1]

ORACLE3_QUBITS = 17
ORACLE3_EXPECTED = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1]

ORACLES = [
(ORACLE1_QUBITS, ORACLE1_EXPECTED),
(ORACLE2_QUBITS, ORACLE2_EXPECTED),
(ORACLE3_QUBITS, ORACLE3_EXPECTED),
]


def grovers_setup(num_qubits: int):
"""
Setup up a circuit for iterations of Grover's search

Places a circuit into uniform superposition. Additionally, places the
ancilla qubit for the oracle in the |-> state such that it can
apply phase kickback.

:param num_qubits: The number of qubits in the circuit
"""
qml.X(num_qubits - 1)
for i in range(num_qubits):
qml.Hadamard(wires=i)


def grovers_mirror(num_qubits):
"""
Apply the "Grover's Mirror" reflection to the active circuit

Performs a reflection across the vector which represents the uniform
superposition. This is used for amplitude-amplification for Grover's
algorithm.

:param num_qubits: The number of qubits in the circuit
"""
for i in range(num_qubits - 1):
qml.Hadamard(wires=i)

qml.MultiControlledX(wires=range(num_qubits), control_values=[False] * (num_qubits - 1))

for i in range(num_qubits - 1):
qml.Hadamard(wires=i)


def run_grovers(oracle, num_qubits):
"""
Overall function for running Grover's algorithm on a chosen oracle

Run Grover's algorithm from start to finish. Prepares a state, and
repeats state selection and amplitude-amplification for sqrt(N) iterations
(where N = 2^(# of non-ancilla qubits)). This implementation assumes that
the oracle always picks precisly 1 state (rather than an arbitrary number).

:param oracle: A black-box function that acts on a created statevector
:param num_qubits: The number of qubits in the circuit the oracle acts
on (includes the ancilla)
"""
grovers_setup(num_qubits)

reps = int(np.sqrt(2 ** (num_qubits - 1)))
for _ in range(reps):
oracle()
grovers_mirror(num_qubits)

return [qml.expval(qml.PauliZ(i)) for i in range(num_qubits - 1)]


def run_experiment(oracle, num_qubits) -> None:
"""
Run Grover's algorithm and evaluates results

Run Grover's algorithm from start to finish, and finds the expected
measurement outcome.

:param oracle: A black-box function that acts on a created statevector
:param num_qubits: The number of qubits in the circuit the oracle acts
on (includes the ancilla)
"""
dev = qml.device("lightning.qubit", wires=num_qubits)

circ = qml.QNode(run_grovers, dev)

expvals = circ(oracle, num_qubits)
results = [int(val < 0) for val in expvals]

print(results)


def gen_oracle(i):
"""
Create an oracle function which selects the state given by the global const

:param i: The index of the globally defined pair of constants to use
"""
num_qubits = ORACLES[i][0]
control_vals = ORACLES[i][1]

def oracle():
qml.MultiControlledX(wires=range(num_qubits), control_values=control_vals)

return (oracle, num_qubits)


if __name__ == "__main__":
import cProfile
import time

# Dummy run to let the interpreter run all functions once
run_experiment(*gen_oracle(0))

def main():
"""
The main function to be run, which executes all experiments and tracks exection times.
"""
times = []
# Run all experiments
for i, pair in enumerate(ORACLES):
print("Expecting:", pair[1])
print("Got:")
start_time = time.time()
run_experiment(*gen_oracle(i))
times.append(time.time() - start_time)
print()

for i, runtime in enumerate(times):
print(f"Time to run oracle {i+1}: {int(1000*runtime)}ms")

cProfile.run("main()", sort="cumtime")
Loading
Loading