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 7 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
14 changes: 14 additions & 0 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 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/)
130 changes: 130 additions & 0 deletions jzaia_files/grover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import numpy as np

Check notice on line 1 in jzaia_files/grover.py

View check run for this annotation

codefactor.io / CodeFactor

jzaia_files/grover.py#L1

Missing module docstring (missing-module-docstring)
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.numpy() < 0) for val in expvals]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of .numpy() suggests that you are running your code with Torch or Tensorflow, for example. Is this the case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be. The python virtualenv I'm using does not have tensorflow nor torch installed. The return type I was getting from running the circuit was a list of pennylane.numpy.tensor.tensor. All of these tensors are 0-dimensional so I used .numpy() to convert these to a scalar value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I take your python file grover.py and run, as it is, in a fresh environment with Python 3.10, where I only installed requirements-dev.txt, I'm getting the following error message:

Traceback (most recent call last):
  File "/home/amintor/Projects/pennylane-lightning/prototypes/jzaia.py", line 114, in <module>
    run_experiment(*gen_oracle(0))
  File "/home/amintor/Projects/pennylane-lightning/prototypes/jzaia.py", line 91, in run_experiment
    results = [int(val.numpy() < 0) for val in expvals]
  File "/home/amintor/Projects/pennylane-lightning/prototypes/jzaia.py", line 91, in <listcomp>
    results = [int(val.numpy() < 0) for val in expvals]
AttributeError: 'float' object has no attribute 'numpy'

Would you know why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After you make all sensible updates, could you please re-run your benchmarks in a new and fresh environment?
Please let us know about your results.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm seeing this too. It seems the issue was that I originally installed my dependencies from requirements.txt instead of requirements-dev.txt. It seems there are only a few differences, but namely the dev version installs Pennylane from source, so this is almost certainly related to that. Fixed in 8bd1f93


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

Check notice on line 111 in jzaia_files/grover.py

View check run for this annotation

codefactor.io / CodeFactor

jzaia_files/grover.py#L111

Consider using enumerate instead of iterating with range and len (consider-using-enumerate)

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

def main():

Check notice on line 116 in jzaia_files/grover.py

View check run for this annotation

codefactor.io / CodeFactor

jzaia_files/grover.py#L116

Missing function or method docstring (missing-function-docstring)
times = []
# Run all experiments
for i in range(len(ORACLES)):
print('Expecting:', ORACLES[i][1])
print('Got:')
start_time = time.time()
run_experiment(*gen_oracle(i))
times.append(time.time() - start_time)
print()

for i in range(len(times)):

Check notice on line 127 in jzaia_files/grover.py

View check run for this annotation

codefactor.io / CodeFactor

jzaia_files/grover.py#L127

Consider using enumerate instead of iterating with range and len (consider-using-enumerate)
print(f'Time to run oracle {i+1}: {int(1000*times[i])}ms')

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