From 0337eb3f5bce6de8d7db9acac72319d6735243d2 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 1 Nov 2024 18:40:10 -0400 Subject: [PATCH 01/14] Get build system working to compile against C++ API --- CMakeLists.txt | 1 + Makefile | 14 ++++++++++++++ jzaia_files/CMakeLists.txt | 7 +++++++ jzaia_files/main.cpp | 12 ++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 jzaia_files/CMakeLists.txt create mode 100644 jzaia_files/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c08304cc19..cdfb92c54b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Makefile b/Makefile index 5973200c52..a49eea549d 100644 --- a/Makefile +++ b/Makefile @@ -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 + cmake -BBuildTests -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_TESTS=OFF \ + -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 \ diff --git a/jzaia_files/CMakeLists.txt b/jzaia_files/CMakeLists.txt new file mode 100644 index 0000000000..7f88fc0cb6 --- /dev/null +++ b/jzaia_files/CMakeLists.txt @@ -0,0 +1,7 @@ +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_algorithms + lightning_qubit_observables + ) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp new file mode 100644 index 0000000000..863686f7bb --- /dev/null +++ b/jzaia_files/main.cpp @@ -0,0 +1,12 @@ +// #include +#include +#include + +using Pennylane::LightningQubit::StateVectorLQubitManaged; + + +int main(void) { + std::cout << "Potato!" << std::endl; + + return 0; +} From 1a78bf02b29133cac9d1953dcf10efd442aa245c Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Tue, 5 Nov 2024 14:55:47 -0500 Subject: [PATCH 02/14] Fully working implementation of Grover's --- Makefile | 4 +- jzaia_files/CMakeLists.txt | 11 +- jzaia_files/main.cpp | 211 ++++++++++++++++++++++++++++++++++++- 3 files changed, 219 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index a49eea549d..8fe93fe3b1 100644 --- a/Makefile +++ b/Makefile @@ -129,10 +129,10 @@ endif ######## Added target: # rm -rf ./BuildTests jzaia-demo: - rm -rf ./BuildTests/jzaia_files ./BuildTests/lq_grover + rm -rf ./BuildTests/jzaia_files ./BuildTests/lq_grover ./BuildTests/lq_grover.dir cmake -BBuildTests -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ - -DBUILD_TESTS=OFF \ + -DBUILD_TESTS=ON \ -DENABLE_WARNINGS=ON \ -DPL_BACKEND=$(PL_BACKEND) \ $(OPTIONS) diff --git a/jzaia_files/CMakeLists.txt b/jzaia_files/CMakeLists.txt index 7f88fc0cb6..780bdb3a3f 100644 --- a/jzaia_files/CMakeLists.txt +++ b/jzaia_files/CMakeLists.txt @@ -2,6 +2,13 @@ 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_algorithms 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/) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index 863686f7bb..cb5d2beef3 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -1,12 +1,217 @@ -// #include -#include +#include +#include #include +#include + +#include "Gates.hpp" +#include "GateImplementationsLM.hpp" +#include "MeasurementsLQubit.hpp" +#include "ObservablesLQubit.hpp" +#include "StateVectorLQubitManaged.hpp" + using Pennylane::LightningQubit::StateVectorLQubitManaged; +using namespace Pennylane::Gates; +using namespace Pennylane::LightningQubit; +using namespace Pennylane::LightningQubit::Measures; +using namespace Pennylane::LightningQubit::Observables; + +// Define values to be selected by each oracle +#define ORACLE1_QUBITS (6) +/* Oracle 1 selects the string: "11010" */ +#define ORACLE1_EXPECTED (std::vector{true, true, false, true, false}) + +#define ORACLE2_QUBITS (10) +/* Oracle 2 selects the string: "101010101" */ +#define ORACLE2_EXPECTED (std::vector{true, false, true, false, true, \ + false, true, false, true}) + + +/** + * @brief Setup up a circuit for iterations of Grover's search + * + * Takes a statevector and places it into uniform superposition. Additionally, + * Places the ancilla qubit for the oracle in the |-> state such that it can + * apply phase kickback. + * + * @param sv The (presumed all |0>) statevector to apply this operation to + */ +void groversSetup(StateVectorLQubitManaged &sv) { + const size_t num_qubits = sv.getNumQubits(); + + // Place target qubit into |1> state so the Hadamard will take it to |-> + GateImplementationsLM::applyPauliX(sv.getData(), + sv.getNumQubits(), + {num_qubits-1}, + false); + + // Set up uniform superposition + for (size_t i = 0; i < num_qubits; ++i) { + GateImplementationsLM::applyHadamard(sv.getData(), + sv.getNumQubits(), + {i}, + false); + } +} + +/** + * @brief Apply the "Grover's Mirror" reflection to the given statevector + * + * Performs a reflection across the vector which represents the uniform + * superposition. This is used for amplitude-amplification for Grover's + * algorithm. + * + * @param sv The statevector to reflect + */ +void groversMirror(StateVectorLQubitManaged &sv) { + const size_t num_qubits = sv.getNumQubits(); + + // Apply H to all non-ancilla qubits + for (size_t i = 0; i < num_qubits-1; ++i) { + GateImplementationsLM::applyHadamard(sv.getData(), + sv.getNumQubits(), + {i}, + false); + } + + std::vector controls(num_qubits-1); + std::vector control_values(num_qubits-1, false); + std::iota(controls.begin(), controls.end(), 0); + + // Apply an anti-controlled on all search qubits X-gate onto the ancilla qubit + GateImplementationsLM::applyNCPauliX(sv.getData(), + sv.getNumQubits(), + controls, + control_values, + {num_qubits-1}, + false); + + // Apply H to all non-ancilla qubits + for (size_t i = 0; i < num_qubits-1; ++i) { + GateImplementationsLM::applyHadamard(sv.getData(), + sv.getNumQubits(), + {i}, + false); + } +} + +/** + * @brief The first testing oracle for Grover's + * + * A 6-qubit test oracle for Grover's algorithm. Applies a pauli-X to the + * rightmost qubit if the leftmost 5 qubits are in the state |11010> + * + * @param sv The statevector to apply the oracle to. Must be 6 qubits + */ +void oracle1(StateVectorLQubitManaged &sv) { + // Sanity check statevector + assert(sv.getNumQubits() == ORACLE1_QUBITS); + + // Define controls to be used for applying the X gate + std::vector controls(ORACLE1_QUBITS-1); + std::iota(controls.begin(), controls.end(), 0); + std::vector control_vals = ORACLE1_EXPECTED; + + // Apply the X gate to the ancilla, controlled on the chosen bitstring + GateImplementationsLM::applyNCPauliX(sv.getData(), + sv.getNumQubits(), + controls, + control_vals, + {ORACLE1_QUBITS-1}, + false); +} + +/** + * @brief The first testing oracle for Grover's + * + * A 10-qubit test oracle for Grover's algorithm. Applies a pauli-X to the + * rightmost qubit if the leftmost 9 qubits are in the state |101010101> + * + * @param sv The statevector to apply the oracle to. Must be 10 qubits + */ +void oracle2(StateVectorLQubitManaged &sv) { + // Sanity check statevector + assert(sv.getNumQubits() == ORACLE2_QUBITS); + + // Define controls to be used for applying the X gate + std::vector controls(ORACLE2_QUBITS-1); + std::iota(controls.begin(), controls.end(), 0); + std::vector control_vals = ORACLE2_EXPECTED; + + // Apply the X gate to the ancilla, controlled on the chosen bitstring + GateImplementationsLM::applyNCPauliX(sv.getData(), + sv.getNumQubits(), + controls, + control_vals, + {ORACLE2_QUBITS-1}, + false); +} + +/** + * @brief Overall function for running Grover's algorithm on a chosen oracle + * + * Run Grover's algorithm from start to finish. Prepares a statevector, 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) + */ +void run_experiment(void (*oracle) (StateVectorLQubitManaged &), + const size_t num_qubits) { + // Create the statevector for this circuit + // NOTE: qubits are numbered left-to-right order + StateVectorLQubitManaged sv(num_qubits); + + // Set up the circuit by placing into uniform superposition + groversSetup(sv); + + // Apply (approx) sqrt(N) repetitions of state selection and amp-amp + const size_t nreps = static_cast(sqrt(1llu << (num_qubits-1))); + for (size_t reps = nreps; reps > 0; --reps) { + // Apply the oracle to apply a phase of -1 to desired state + oracle(sv); + // Perform amp-amp by reflecting over |+++...+> + groversMirror(sv); + } + + // Set up measurements + Measurements> Measurer(sv); + // Vector to store the most common measurement outcome + std::vector common_result(num_qubits - 1, false); + + // Perform a "measurement" by taking the expected value of this qubit over multiple runs + for (size_t obs_wire=0; obs_wire < num_qubits - 1; ++obs_wire) { + NamedObs> obs("PauliZ", {obs_wire}); + double result = Measurer.expval(obs); + common_result[obs_wire] = (result < 0); + } + + // // Commented out: Print the raw statevector output (too large and messy) + // std::vector wires(num_qubits-1); + // for (size_t i = 0; i < num_qubits - 1; ++i) { wires[i] = i; } + // std::vector probabilities = Measurer.probs(wires); + + // size_t qubitString = 0; + // for (auto prob : probabilities) { + // std::cout << qubitString << ": " << prob << std::endl; + // ++qubitString; + // } + + std::cout << "Measured results (most common with expval): " << common_result << std::endl; +} int main(void) { - std::cout << "Potato!" << std::endl; + // Run experiment 1: 11010 + std::cout << "Expected: " << ORACLE1_EXPECTED << std::endl; + run_experiment(oracle1, ORACLE1_QUBITS); + + // Run experiment 2: 101010101 + std::cout << "Expected: " << ORACLE2_EXPECTED << std::endl; + run_experiment(oracle2, ORACLE2_QUBITS); return 0; } From 906974e977ed4001af7b28d3c846f12a2a046b00 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Tue, 5 Nov 2024 15:00:59 -0500 Subject: [PATCH 03/14] Introduce extra large 3rd oracle --- jzaia_files/main.cpp | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index cb5d2beef3..acee0e8933 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -26,6 +26,13 @@ using namespace Pennylane::LightningQubit::Observables; #define ORACLE2_EXPECTED (std::vector{true, false, true, false, true, \ false, true, false, true}) +#define ORACLE3_QUBITS (17) +/* Oracle 2 selects the string: "0011001100110011" */ +#define ORACLE3_EXPECTED (std::vector{false, false, true, true, false, \ + false, true, true, false, false, \ + true, true, false, false, true, \ + true}) + /** * @brief Setup up a circuit for iterations of Grover's search @@ -122,7 +129,7 @@ void oracle1(StateVectorLQubitManaged &sv) { } /** - * @brief The first testing oracle for Grover's + * @brief The second testing oracle for Grover's * * A 10-qubit test oracle for Grover's algorithm. Applies a pauli-X to the * rightmost qubit if the leftmost 9 qubits are in the state |101010101> @@ -147,6 +154,33 @@ void oracle2(StateVectorLQubitManaged &sv) { false); } +/** + * @brief The third testing oracle for Grover's + * + * A 17-qubit test oracle for Grover's algorithm. Applies a pauli-X to the + * rightmost qubit if the leftmost 16 qubits are in the state |0011001100110011> + * + * @param sv The statevector to apply the oracle to. Must be 17 qubits + */ +void oracle3(StateVectorLQubitManaged &sv) { + // Sanity check statevector + assert(sv.getNumQubits() == ORACLE3_QUBITS); + + // Define controls to be used for applying the X gate + std::vector controls(ORACLE3_QUBITS-1); + std::iota(controls.begin(), controls.end(), 0); + std::vector control_vals = ORACLE3_EXPECTED; + + // Apply the X gate to the ancilla, controlled on the chosen bitstring + GateImplementationsLM::applyNCPauliX(sv.getData(), + sv.getNumQubits(), + controls, + control_vals, + {ORACLE3_QUBITS-1}, + false); +} + + /** * @brief Overall function for running Grover's algorithm on a chosen oracle * @@ -213,5 +247,9 @@ int main(void) { std::cout << "Expected: " << ORACLE2_EXPECTED << std::endl; run_experiment(oracle2, ORACLE2_QUBITS); + // Run experiment 3: 0011001100110011 + std::cout << "Expected: " << ORACLE3_EXPECTED << std::endl; + run_experiment(oracle3, ORACLE3_QUBITS); + return 0; } From 48dea6c0661b4015423e879e7a81ee9989a692d7 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Tue, 5 Nov 2024 16:23:55 -0500 Subject: [PATCH 04/14] Implement identical algo to C++ impl with python+pennylane --- jzaia_files/grover.py | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 jzaia_files/grover.py diff --git a/jzaia_files/grover.py b/jzaia_files/grover.py new file mode 100644 index 0000000000..40ad111a59 --- /dev/null +++ b/jzaia_files/grover.py @@ -0,0 +1,115 @@ +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.numpy() < 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__': + # Run all experiments + for i in range(len(ORACLES)): + print('Expecting:', ORACLES[i][1]) + print('Got:') + run_experiment(*gen_oracle(i)) + print() From 3b54a2aa0292e1bede59b990e9e62e9d4a32064a Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Wed, 6 Nov 2024 16:51:08 -0500 Subject: [PATCH 05/14] Add benchmarking code to both C++ and python impls --- jzaia_files/grover.py | 27 +++++++++++++++++++++------ jzaia_files/main.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 4 ++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/jzaia_files/grover.py b/jzaia_files/grover.py index 40ad111a59..d704c2543b 100644 --- a/jzaia_files/grover.py +++ b/jzaia_files/grover.py @@ -107,9 +107,24 @@ def oracle(): if __name__ == '__main__': - # Run all experiments - for i in range(len(ORACLES)): - print('Expecting:', ORACLES[i][1]) - print('Got:') - run_experiment(*gen_oracle(i)) - print() + import cProfile + import time + + # Dummy run to let the interpreter run all functions once + run_experiment(*gen_oracle(0)) + + def main(): + 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)): + print(f'Time to run oracle {i+1}: {int(1000*times[i])}ms') + + cProfile.run('main()', sort='cumtime') diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index acee0e8933..5c235d29aa 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -239,17 +240,52 @@ void run_experiment(void (*oracle) (StateVectorLQubitManaged &), int main(void) { + using std::chrono::high_resolution_clock; + using std::chrono::duration_cast; + using std::chrono::duration; + using std::chrono::milliseconds; + + // Run experiment 1: 11010 - std::cout << "Expected: " << ORACLE1_EXPECTED << std::endl; + std::cout << "Running Oracle 1. Expected: " << ORACLE1_EXPECTED << std::endl; + auto start_time = high_resolution_clock::now(); + run_experiment(oracle1, ORACLE1_QUBITS); + const duration time_oracle1 = duration_cast( + high_resolution_clock::now() - start_time); + + std::cout << std::endl; + + // Run experiment 2: 101010101 - std::cout << "Expected: " << ORACLE2_EXPECTED << std::endl; + std::cout << "Running Oracle 2. Expected: " << ORACLE2_EXPECTED << std::endl; + start_time = high_resolution_clock::now(); + run_experiment(oracle2, ORACLE2_QUBITS); + const duration time_oracle2 = duration_cast( + high_resolution_clock::now() - start_time); + + std::cout << std::endl; + + + // Run experiment 3: 0011001100110011 - std::cout << "Expected: " << ORACLE3_EXPECTED << std::endl; + std::cout << "Running Oracle 3. Expected: " << ORACLE3_EXPECTED << std::endl; + start_time = high_resolution_clock::now(); + run_experiment(oracle3, ORACLE3_QUBITS); + const duration time_oracle3 = duration_cast( + high_resolution_clock::now() - start_time); + + std::cout << std::endl; + + + std::cout << "Time to run oracle 1: " << time_oracle1.count() << "ms\n"; + std::cout << "Time to run oracle 2: " << time_oracle2.count() << "ms\n"; + std::cout << "Time to run oracle 3: " << time_oracle3.count() << "ms\n"; + return 0; } diff --git a/pyproject.toml b/pyproject.toml index d2e348a5bc..88aa544e02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["cmake~=3.27.0", "ninja; platform_system!='Windows'", "setuptools>=42", "tomli"] +requires = [ "cmake~=3.27.0", "ninja; platform_system!='Windows'", "setuptools>=42", "tomli",] build-backend = "setuptools.build_meta" [project] @@ -8,7 +8,7 @@ description = "PennyLane-Lightning plugin" readme = "README.rst" requires-python = ">=3.10" classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Physics",] -dependencies = ["pennylane>=0.37"] +dependencies = [ "pennylane>=0.37",] dynamic = [ "version",] [[project.maintainers]] name = "Xanadu Quantum Technologies Inc." From 1d658fb93e2ba41e55aa66cb6f036eba98c7b1a3 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Thu, 7 Nov 2024 17:56:51 -0500 Subject: [PATCH 06/14] Change result extraction to use functional programming style --- jzaia_files/main.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index 5c235d29aa..4c0e6d47b5 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -215,14 +216,21 @@ void run_experiment(void (*oracle) (StateVectorLQubitManaged &), // Set up measurements Measurements> Measurer(sv); // Vector to store the most common measurement outcome + std::vector wires(num_qubits - 1); + std::iota(wires.begin(), wires.end(), 0); std::vector common_result(num_qubits - 1, false); + std::transform(wires.begin(), wires.end(), common_result.begin(), [&Measurer](size_t wire){ + NamedObs> obs("PauliZ", {wire}); + return Measurer.expval(obs) < 0; + }); + // Perform a "measurement" by taking the expected value of this qubit over multiple runs - for (size_t obs_wire=0; obs_wire < num_qubits - 1; ++obs_wire) { - NamedObs> obs("PauliZ", {obs_wire}); - double result = Measurer.expval(obs); - common_result[obs_wire] = (result < 0); - } + // for (size_t obs_wire=0; obs_wire < num_qubits - 1; ++obs_wire) { + // NamedObs> obs("PauliZ", {obs_wire}); + // double result = Measurer.expval(obs); + // common_result[obs_wire] = (result < 0); + // } // // Commented out: Print the raw statevector output (too large and messy) // std::vector wires(num_qubits-1); From 5057894f184abe7052dd37938e46079e2a052d1d Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Thu, 7 Nov 2024 18:12:56 -0500 Subject: [PATCH 07/14] Improve application of oracles by condensing into 1 function --- jzaia_files/main.cpp | 154 +++++++++++-------------------------------- 1 file changed, 40 insertions(+), 114 deletions(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index 4c0e6d47b5..ccc5e0657e 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -111,78 +111,26 @@ void groversMirror(StateVectorLQubitManaged &sv) { * rightmost qubit if the leftmost 5 qubits are in the state |11010> * * @param sv The statevector to apply the oracle to. Must be 6 qubits + * @param control_vals The state the oracle should select */ -void oracle1(StateVectorLQubitManaged &sv) { +void oracle(StateVectorLQubitManaged &sv, + const std::vector &control_vals) { // Sanity check statevector - assert(sv.getNumQubits() == ORACLE1_QUBITS); + assert(sv.getNumQubits() == control_vals.size()+1); // Define controls to be used for applying the X gate - std::vector controls(ORACLE1_QUBITS-1); + std::vector controls(control_vals.size()); std::iota(controls.begin(), controls.end(), 0); - std::vector control_vals = ORACLE1_EXPECTED; // Apply the X gate to the ancilla, controlled on the chosen bitstring GateImplementationsLM::applyNCPauliX(sv.getData(), sv.getNumQubits(), controls, control_vals, - {ORACLE1_QUBITS-1}, + {control_vals.size()}, false); } -/** - * @brief The second testing oracle for Grover's - * - * A 10-qubit test oracle for Grover's algorithm. Applies a pauli-X to the - * rightmost qubit if the leftmost 9 qubits are in the state |101010101> - * - * @param sv The statevector to apply the oracle to. Must be 10 qubits - */ -void oracle2(StateVectorLQubitManaged &sv) { - // Sanity check statevector - assert(sv.getNumQubits() == ORACLE2_QUBITS); - - // Define controls to be used for applying the X gate - std::vector controls(ORACLE2_QUBITS-1); - std::iota(controls.begin(), controls.end(), 0); - std::vector control_vals = ORACLE2_EXPECTED; - - // Apply the X gate to the ancilla, controlled on the chosen bitstring - GateImplementationsLM::applyNCPauliX(sv.getData(), - sv.getNumQubits(), - controls, - control_vals, - {ORACLE2_QUBITS-1}, - false); -} - -/** - * @brief The third testing oracle for Grover's - * - * A 17-qubit test oracle for Grover's algorithm. Applies a pauli-X to the - * rightmost qubit if the leftmost 16 qubits are in the state |0011001100110011> - * - * @param sv The statevector to apply the oracle to. Must be 17 qubits - */ -void oracle3(StateVectorLQubitManaged &sv) { - // Sanity check statevector - assert(sv.getNumQubits() == ORACLE3_QUBITS); - - // Define controls to be used for applying the X gate - std::vector controls(ORACLE3_QUBITS-1); - std::iota(controls.begin(), controls.end(), 0); - std::vector control_vals = ORACLE3_EXPECTED; - - // Apply the X gate to the ancilla, controlled on the chosen bitstring - GateImplementationsLM::applyNCPauliX(sv.getData(), - sv.getNumQubits(), - controls, - control_vals, - {ORACLE3_QUBITS-1}, - false); -} - - /** * @brief Overall function for running Grover's algorithm on a chosen oracle * @@ -191,12 +139,12 @@ void oracle3(StateVectorLQubitManaged &sv) { * (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) + * @param expected The state which the oracle should apply phase to */ -void run_experiment(void (*oracle) (StateVectorLQubitManaged &), - const size_t num_qubits) { +void run_experiment(const size_t num_qubits, + const std::vector &expected) { // Create the statevector for this circuit // NOTE: qubits are numbered left-to-right order StateVectorLQubitManaged sv(num_qubits); @@ -208,7 +156,7 @@ void run_experiment(void (*oracle) (StateVectorLQubitManaged &), const size_t nreps = static_cast(sqrt(1llu << (num_qubits-1))); for (size_t reps = nreps; reps > 0; --reps) { // Apply the oracle to apply a phase of -1 to desired state - oracle(sv); + oracle(sv, expected); // Perform amp-amp by reflecting over |+++...+> groversMirror(sv); } @@ -220,28 +168,14 @@ void run_experiment(void (*oracle) (StateVectorLQubitManaged &), std::iota(wires.begin(), wires.end(), 0); std::vector common_result(num_qubits - 1, false); - std::transform(wires.begin(), wires.end(), common_result.begin(), [&Measurer](size_t wire){ - NamedObs> obs("PauliZ", {wire}); - return Measurer.expval(obs) < 0; - }); - // Perform a "measurement" by taking the expected value of this qubit over multiple runs - // for (size_t obs_wire=0; obs_wire < num_qubits - 1; ++obs_wire) { - // NamedObs> obs("PauliZ", {obs_wire}); - // double result = Measurer.expval(obs); - // common_result[obs_wire] = (result < 0); - // } - - // // Commented out: Print the raw statevector output (too large and messy) - // std::vector wires(num_qubits-1); - // for (size_t i = 0; i < num_qubits - 1; ++i) { wires[i] = i; } - // std::vector probabilities = Measurer.probs(wires); - - // size_t qubitString = 0; - // for (auto prob : probabilities) { - // std::cout << qubitString << ": " << prob << std::endl; - // ++qubitString; - // } + std::transform(wires.begin(), + wires.end(), + common_result.begin(), + [&Measurer](size_t wire){ + NamedObs> obs("PauliZ", {wire}); + return Measurer.expval(obs) < 0; + }); std::cout << "Measured results (most common with expval): " << common_result << std::endl; } @@ -254,46 +188,38 @@ int main(void) { using std::chrono::milliseconds; - // Run experiment 1: 11010 - std::cout << "Running Oracle 1. Expected: " << ORACLE1_EXPECTED << std::endl; - auto start_time = high_resolution_clock::now(); - - run_experiment(oracle1, ORACLE1_QUBITS); - - const duration time_oracle1 = duration_cast( - high_resolution_clock::now() - start_time); - - std::cout << std::endl; - + std::vector>> inputs = { + // Experiment 1: 11010 + {ORACLE1_QUBITS, ORACLE1_EXPECTED}, + // Experiment 2: 101010101 + {ORACLE2_QUBITS, ORACLE2_EXPECTED}, + // Experiment 3: 0011001100110011 + {ORACLE3_QUBITS, ORACLE3_EXPECTED}, + }; - // Run experiment 2: 101010101 - std::cout << "Running Oracle 2. Expected: " << ORACLE2_EXPECTED << std::endl; - start_time = high_resolution_clock::now(); + std::vector runtimes(inputs.size()); - run_experiment(oracle2, ORACLE2_QUBITS); + for (size_t i = 0; i < inputs.size(); ++i) { + auto num_qubits = inputs[i].first; + auto expected = inputs[i].second; - const duration time_oracle2 = duration_cast( - high_resolution_clock::now() - start_time); + std::cout << "Running Oracle " << i+1 << ". Expected: "; + std::cout << expected << std::endl; - std::cout << std::endl; + auto start_time = high_resolution_clock::now(); + run_experiment(num_qubits, expected); + runtimes[i] = duration_cast( + high_resolution_clock::now() - start_time); - // Run experiment 3: 0011001100110011 - std::cout << "Running Oracle 3. Expected: " << ORACLE3_EXPECTED << std::endl; - start_time = high_resolution_clock::now(); - - run_experiment(oracle3, ORACLE3_QUBITS); - - const duration time_oracle3 = duration_cast( - high_resolution_clock::now() - start_time); - - std::cout << std::endl; + std::cout << std::endl; + } - std::cout << "Time to run oracle 1: " << time_oracle1.count() << "ms\n"; - std::cout << "Time to run oracle 2: " << time_oracle2.count() << "ms\n"; - std::cout << "Time to run oracle 3: " << time_oracle3.count() << "ms\n"; + for (size_t i = 0; i < runtimes.size(); ++i) { + std::cout << "Time to run oracle " << i+1 << ": " << runtimes[i].count() << "ms\n"; + } return 0; } From aa94a182e5d496a8bc0dcac1b49401f8fdf0cbca Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:01:40 -0500 Subject: [PATCH 08/14] Add custom directory to format target, and format files --- Makefile | 5 +- jzaia_files/grover.py | 71 +++++++++++++++------------- jzaia_files/main.cpp | 106 ++++++++++++++++++------------------------ 3 files changed, 86 insertions(+), 96 deletions(-) diff --git a/Makefile b/Makefile index 8fe93fe3b1..e754e33917 100644 --- a/Makefile +++ b/Makefile @@ -166,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: diff --git a/jzaia_files/grover.py b/jzaia_files/grover.py index d704c2543b..b0f35635e0 100644 --- a/jzaia_files/grover.py +++ b/jzaia_files/grover.py @@ -2,22 +2,24 @@ import pennylane as qml # Define oracles -ORACLE1_QUBITS = 6 +ORACLE1_QUBITS = 6 ORACLE1_EXPECTED = [1, 1, 0, 1, 0] -ORACLE2_QUBITS = 10 +ORACLE2_QUBITS = 10 ORACLE2_EXPECTED = [1, 0, 1, 0, 1, 0, 1, 0, 1] -ORACLE3_QUBITS = 17 +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)] +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 @@ -25,13 +27,14 @@ def grovers_setup(num_qubits: int): apply phase kickback. :param num_qubits: The number of qubits in the circuit - ''' - qml.X(num_qubits-1) + """ + 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 @@ -39,18 +42,18 @@ def grovers_mirror(num_qubits): algorithm. :param num_qubits: The number of qubits in the circuit - ''' - for i in range(num_qubits-1): + """ + for i in range(num_qubits - 1): qml.Hadamard(wires=i) - qml.MultiControlledX(wires=range(num_qubits), - control_values=[False]*(num_qubits-1)) + qml.MultiControlledX(wires=range(num_qubits), control_values=[False] * (num_qubits - 1)) - for i in range(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 @@ -61,18 +64,19 @@ def run_grovers(oracle, num_qubits): :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))) + 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)] + 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 @@ -81,9 +85,8 @@ def run_experiment(oracle, num_qubits) -> None: :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) - + """ + dev = qml.device("lightning.qubit", wires=num_qubits) circ = qml.QNode(run_grovers, dev) @@ -92,21 +95,23 @@ def run_experiment(oracle, num_qubits) -> None: 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] + """ + num_qubits = ORACLES[i][0] control_vals = ORACLES[i][1] + def oracle(): - qml.MultiControlledX(wires=range(num_qubits), - control_values=control_vals) + qml.MultiControlledX(wires=range(num_qubits), control_values=control_vals) + return (oracle, num_qubits) -if __name__ == '__main__': +if __name__ == "__main__": import cProfile import time @@ -117,14 +122,14 @@ def main(): times = [] # Run all experiments for i in range(len(ORACLES)): - print('Expecting:', ORACLES[i][1]) - print('Got:') + 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)): - print(f'Time to run oracle {i+1}: {int(1000*times[i])}ms') + print(f"Time to run oracle {i+1}: {int(1000*times[i])}ms") - cProfile.run('main()', sort='cumtime') + cProfile.run("main()", sort="cumtime") diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index ccc5e0657e..a463fd86ff 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -5,13 +5,12 @@ #include #include -#include "Gates.hpp" #include "GateImplementationsLM.hpp" +#include "Gates.hpp" #include "MeasurementsLQubit.hpp" #include "ObservablesLQubit.hpp" #include "StateVectorLQubitManaged.hpp" - using Pennylane::LightningQubit::StateVectorLQubitManaged; using namespace Pennylane::Gates; using namespace Pennylane::LightningQubit; @@ -25,16 +24,15 @@ using namespace Pennylane::LightningQubit::Observables; #define ORACLE2_QUBITS (10) /* Oracle 2 selects the string: "101010101" */ -#define ORACLE2_EXPECTED (std::vector{true, false, true, false, true, \ - false, true, false, true}) +#define ORACLE2_EXPECTED \ + (std::vector{true, false, true, false, true, false, true, false, \ + true}) #define ORACLE3_QUBITS (17) /* Oracle 2 selects the string: "0011001100110011" */ -#define ORACLE3_EXPECTED (std::vector{false, false, true, true, false, \ - false, true, true, false, false, \ - true, true, false, false, true, \ - true}) - +#define ORACLE3_EXPECTED \ + (std::vector{false, false, true, true, false, false, true, true, \ + false, false, true, true, false, false, true, true}) /** * @brief Setup up a circuit for iterations of Grover's search @@ -49,17 +47,13 @@ void groversSetup(StateVectorLQubitManaged &sv) { const size_t num_qubits = sv.getNumQubits(); // Place target qubit into |1> state so the Hadamard will take it to |-> - GateImplementationsLM::applyPauliX(sv.getData(), - sv.getNumQubits(), - {num_qubits-1}, - false); + GateImplementationsLM::applyPauliX(sv.getData(), sv.getNumQubits(), + {num_qubits - 1}, false); // Set up uniform superposition for (size_t i = 0; i < num_qubits; ++i) { - GateImplementationsLM::applyHadamard(sv.getData(), - sv.getNumQubits(), - {i}, - false); + GateImplementationsLM::applyHadamard(sv.getData(), sv.getNumQubits(), + {i}, false); } } @@ -76,31 +70,25 @@ void groversMirror(StateVectorLQubitManaged &sv) { const size_t num_qubits = sv.getNumQubits(); // Apply H to all non-ancilla qubits - for (size_t i = 0; i < num_qubits-1; ++i) { - GateImplementationsLM::applyHadamard(sv.getData(), - sv.getNumQubits(), - {i}, - false); + for (size_t i = 0; i < num_qubits - 1; ++i) { + GateImplementationsLM::applyHadamard(sv.getData(), sv.getNumQubits(), + {i}, false); } - std::vector controls(num_qubits-1); - std::vector control_values(num_qubits-1, false); + std::vector controls(num_qubits - 1); + std::vector control_values(num_qubits - 1, false); std::iota(controls.begin(), controls.end(), 0); - // Apply an anti-controlled on all search qubits X-gate onto the ancilla qubit - GateImplementationsLM::applyNCPauliX(sv.getData(), - sv.getNumQubits(), - controls, - control_values, - {num_qubits-1}, - false); + // Apply an anti-controlled on all search qubits X-gate onto the ancilla + // qubit + GateImplementationsLM::applyNCPauliX(sv.getData(), sv.getNumQubits(), + controls, control_values, + {num_qubits - 1}, false); // Apply H to all non-ancilla qubits - for (size_t i = 0; i < num_qubits-1; ++i) { - GateImplementationsLM::applyHadamard(sv.getData(), - sv.getNumQubits(), - {i}, - false); + for (size_t i = 0; i < num_qubits - 1; ++i) { + GateImplementationsLM::applyHadamard(sv.getData(), sv.getNumQubits(), + {i}, false); } } @@ -116,19 +104,16 @@ void groversMirror(StateVectorLQubitManaged &sv) { void oracle(StateVectorLQubitManaged &sv, const std::vector &control_vals) { // Sanity check statevector - assert(sv.getNumQubits() == control_vals.size()+1); + assert(sv.getNumQubits() == control_vals.size() + 1); // Define controls to be used for applying the X gate std::vector controls(control_vals.size()); std::iota(controls.begin(), controls.end(), 0); // Apply the X gate to the ancilla, controlled on the chosen bitstring - GateImplementationsLM::applyNCPauliX(sv.getData(), - sv.getNumQubits(), - controls, - control_vals, - {control_vals.size()}, - false); + GateImplementationsLM::applyNCPauliX(sv.getData(), sv.getNumQubits(), + controls, control_vals, + {control_vals.size()}, false); } /** @@ -153,7 +138,7 @@ void run_experiment(const size_t num_qubits, groversSetup(sv); // Apply (approx) sqrt(N) repetitions of state selection and amp-amp - const size_t nreps = static_cast(sqrt(1llu << (num_qubits-1))); + const size_t nreps = static_cast(sqrt(1llu << (num_qubits - 1))); for (size_t reps = nreps; reps > 0; --reps) { // Apply the oracle to apply a phase of -1 to desired state oracle(sv, expected); @@ -168,26 +153,25 @@ void run_experiment(const size_t num_qubits, std::iota(wires.begin(), wires.end(), 0); std::vector common_result(num_qubits - 1, false); - // Perform a "measurement" by taking the expected value of this qubit over multiple runs - std::transform(wires.begin(), - wires.end(), - common_result.begin(), - [&Measurer](size_t wire){ - NamedObs> obs("PauliZ", {wire}); + // Perform a "measurement" by taking the expected value of this qubit over + // multiple runs + std::transform(wires.begin(), wires.end(), common_result.begin(), + [&Measurer](size_t wire) { + NamedObs> obs("PauliZ", + {wire}); return Measurer.expval(obs) < 0; }); - std::cout << "Measured results (most common with expval): " << common_result << std::endl; + std::cout << "Measured results (most common with expval): " << common_result + << std::endl; } - int main(void) { - using std::chrono::high_resolution_clock; - using std::chrono::duration_cast; using std::chrono::duration; + using std::chrono::duration_cast; + using std::chrono::high_resolution_clock; using std::chrono::milliseconds; - std::vector>> inputs = { // Experiment 1: 11010 {ORACLE1_QUBITS, ORACLE1_EXPECTED}, @@ -201,24 +185,24 @@ int main(void) { for (size_t i = 0; i < inputs.size(); ++i) { auto num_qubits = inputs[i].first; - auto expected = inputs[i].second; + auto expected = inputs[i].second; - std::cout << "Running Oracle " << i+1 << ". Expected: "; + std::cout << "Running Oracle " << i + 1 << ". Expected: "; std::cout << expected << std::endl; auto start_time = high_resolution_clock::now(); run_experiment(num_qubits, expected); - runtimes[i] = duration_cast( - high_resolution_clock::now() - start_time); + runtimes[i] = duration_cast(high_resolution_clock::now() - + start_time); std::cout << std::endl; - } for (size_t i = 0; i < runtimes.size(); ++i) { - std::cout << "Time to run oracle " << i+1 << ": " << runtimes[i].count() << "ms\n"; + std::cout << "Time to run oracle " << i + 1 << ": " + << runtimes[i].count() << "ms\n"; } return 0; From e466e97c55eaae60d452586f02a9c2601f9c85e6 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:07:01 -0500 Subject: [PATCH 09/14] Fix comment rot on oracle function --- jzaia_files/main.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index a463fd86ff..a77e2cef7a 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -93,12 +93,13 @@ void groversMirror(StateVectorLQubitManaged &sv) { } /** - * @brief The first testing oracle for Grover's + * @brief A generalized oracle function for Grover's algorithm * - * A 6-qubit test oracle for Grover's algorithm. Applies a pauli-X to the - * rightmost qubit if the leftmost 5 qubits are in the state |11010> + * Applies an oracle for Grover's algorithm. Applies a pauli-X to the + * rightmost qubit if the leftmost qubits are in the state specified by + * `control_vals`. Only selects a single state * - * @param sv The statevector to apply the oracle to. Must be 6 qubits + * @param sv The statevector to apply the oracle to * @param control_vals The state the oracle should select */ void oracle(StateVectorLQubitManaged &sv, From 8bd1f9344b4fb7189f33847abd87aa6fc3ed5fa5 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:13:23 -0500 Subject: [PATCH 10/14] Fix type mismatch caused by attempt to unpack tensor as a numpy object --- jzaia_files/grover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jzaia_files/grover.py b/jzaia_files/grover.py index b0f35635e0..f8e0a24f50 100644 --- a/jzaia_files/grover.py +++ b/jzaia_files/grover.py @@ -91,7 +91,7 @@ def run_experiment(oracle, num_qubits) -> None: circ = qml.QNode(run_grovers, dev) expvals = circ(oracle, num_qubits) - results = [int(val.numpy() < 0) for val in expvals] + results = [int(val < 0) for val in expvals] print(results) From 0066b3b603b05459e69c0db5b32540b47fc48f6c Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:17:27 -0500 Subject: [PATCH 11/14] Change loop over grover repetitions to forward iterations --- jzaia_files/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index a77e2cef7a..fc34a39d69 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -140,7 +140,7 @@ void run_experiment(const size_t num_qubits, // Apply (approx) sqrt(N) repetitions of state selection and amp-amp const size_t nreps = static_cast(sqrt(1llu << (num_qubits - 1))); - for (size_t reps = nreps; reps > 0; --reps) { + for (size_t reps = 0; reps < nreps; ++reps) { // Apply the oracle to apply a phase of -1 to desired state oracle(sv, expected); // Perform amp-amp by reflecting over |+++...+> From ff86a5ec7c170c2a723397af8918b0ba18b9b8b7 Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:25:37 -0500 Subject: [PATCH 12/14] Refactor macros for oracle definitions into global consts --- jzaia_files/main.cpp | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index fc34a39d69..e85cde8474 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -18,21 +18,14 @@ using namespace Pennylane::LightningQubit::Measures; using namespace Pennylane::LightningQubit::Observables; // Define values to be selected by each oracle -#define ORACLE1_QUBITS (6) -/* Oracle 1 selects the string: "11010" */ -#define ORACLE1_EXPECTED (std::vector{true, true, false, true, false}) - -#define ORACLE2_QUBITS (10) -/* Oracle 2 selects the string: "101010101" */ -#define ORACLE2_EXPECTED \ - (std::vector{true, false, true, false, true, false, true, false, \ - true}) - -#define ORACLE3_QUBITS (17) -/* Oracle 2 selects the string: "0011001100110011" */ -#define ORACLE3_EXPECTED \ - (std::vector{false, false, true, true, false, false, true, true, \ - false, false, true, true, false, false, true, true}) +const std::pair> oracle1_data = { + 6, std::vector{true, true, false, true, false}}; +const std::pair> oracle2_data = { + 10, std::vector{true, false, true, false, true, false, true, false, + true}}; +const std::pair> oracle3_data = { + 17, std::vector{false, false, true, true, false, false, true, true, + false, false, true, true, false, false, true, true}}; /** * @brief Setup up a circuit for iterations of Grover's search @@ -175,11 +168,11 @@ int main(void) { std::vector>> inputs = { // Experiment 1: 11010 - {ORACLE1_QUBITS, ORACLE1_EXPECTED}, + oracle1_data, // Experiment 2: 101010101 - {ORACLE2_QUBITS, ORACLE2_EXPECTED}, + oracle2_data, // Experiment 3: 0011001100110011 - {ORACLE3_QUBITS, ORACLE3_EXPECTED}, + oracle3_data, }; std::vector runtimes(inputs.size()); From ca6634ca4d8dd027afbb9bbd956b003ee81f689f Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:33:09 -0500 Subject: [PATCH 13/14] Condense creation of results vector into a single STL function call, remove unneeded vector --- jzaia_files/main.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/jzaia_files/main.cpp b/jzaia_files/main.cpp index e85cde8474..2cbd1034a0 100644 --- a/jzaia_files/main.cpp +++ b/jzaia_files/main.cpp @@ -143,18 +143,16 @@ void run_experiment(const size_t num_qubits, // Set up measurements Measurements> Measurer(sv); // Vector to store the most common measurement outcome - std::vector wires(num_qubits - 1); - std::iota(wires.begin(), wires.end(), 0); std::vector common_result(num_qubits - 1, false); // Perform a "measurement" by taking the expected value of this qubit over // multiple runs - std::transform(wires.begin(), wires.end(), common_result.begin(), - [&Measurer](size_t wire) { - NamedObs> obs("PauliZ", - {wire}); - return Measurer.expval(obs) < 0; - }); + std::generate(common_result.begin(), common_result.end(), + [&Measurer, wire = 0llu]() mutable { + NamedObs> obs("PauliZ", + {wire++}); + return Measurer.expval(obs) < 0; + }); std::cout << "Measured results (most common with expval): " << common_result << std::endl; From a5ecaa0bdf0c03c1be0be7a023466255f3004bfe Mon Sep 17 00:00:00 2001 From: Jake Zaia Date: Fri, 8 Nov 2024 11:41:13 -0500 Subject: [PATCH 14/14] Formatting changes to Python impl to please code-coverage plugin --- jzaia_files/grover.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/jzaia_files/grover.py b/jzaia_files/grover.py index f8e0a24f50..2107f432f5 100644 --- a/jzaia_files/grover.py +++ b/jzaia_files/grover.py @@ -1,3 +1,8 @@ +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 @@ -119,17 +124,20 @@ def oracle(): 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 in range(len(ORACLES)): - print("Expecting:", ORACLES[i][1]) + 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 in range(len(times)): - print(f"Time to run oracle {i+1}: {int(1000*times[i])}ms") + for i, runtime in enumerate(times): + print(f"Time to run oracle {i+1}: {int(1000*runtime)}ms") cProfile.run("main()", sort="cumtime")