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

Fixes #975 : Implement QFT in lightning.qubit and benchmark #990

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

gzquse
Copy link

@gzquse gzquse commented Nov 8, 2024

Dear Thomas,

Solution from my side

  • add support for the Quantum Fourier Transform (QFT) gate in the OpToMemberFuncPtr.hpp file

Add the Struct: Add a new specialization of the GateOpToMemberFuncPtr template for the QFT gate.
Implement member

/**
* @brief Pointer to apply the Quantum Fourier Transform (QFT) operation.
*/
template <class PrecisionT, class ParamT, class GateImplementation>
struct GateOpToMemberFuncPtr<PrecisionT, ParamT, GateImplementation,
                             GateOperation::QFT> {
    using Type = void (GateImplementation::*)(std::complex<PrecisionT> *);
    constexpr static Type value = &GateImplementation::template applyQFT<PrecisionT>;
};
  • update the list of supported gates to include the QFT gate in the lightning_qubit.py
_operations = frozenset(
    {
        "Identity",
        "QubitUnitary",
        "MultiControlledX",
        "DiagonalQubitUnitary",
        "PauliX",
        "PauliY",
        "PauliZ",
        "MultiRZ",
        "GlobalPhase",
        "Hadamard",
        "S",
        "Adjoint(S)",
        "T",
        "Adjoint(T)",
        "SX",
        "Adjoint(SX)",
        "CNOT",
        "SWAP",
        "ISWAP",
        "PSWAP",
        "Adjoint(ISWAP)",
        "SISWAP",
        "Adjoint(SISWAP)",
        "SQISW",
        "CSWAP",
        "Toffoli",
        "CY",
        "CZ",
        "PhaseShift",
        "ControlledPhaseShift",
        "RX",
        "RY",
        "RZ",
        "Rot",
        "CRX",
        "CRY",
        "CRZ",
        "C(PauliX)",
        "C(PauliY)",
        "C(PauliZ)",
        "C(Hadamard)",
        "C(S)",
        "C(T)",
        "C(PhaseShift)",
        "C(RX)",
        "C(RY)",
        "C(RZ)",
        "C(Rot)",
        "C(SWAP)",
        "C(IsingXX)",
        "C(IsingXY)",
        "C(IsingYY)",
        "C(IsingZZ)",
        "C(SingleExcitation)",
        "C(SingleExcitationMinus)",
        "C(SingleExcitationPlus)",
        "C(DoubleExcitation)",
        "C(DoubleExcitationMinus)",
        "C(DoubleExcitationPlus)",
        "C(MultiRZ)",
        "C(GlobalPhase)",
        "C(QubitUnitary)",
        "CRot",
        "IsingXX",
        "IsingYY",
        "IsingZZ",
        "IsingXY",
        "SingleExcitation",
        "SingleExcitationPlus",
        "SingleExcitationMinus",
        "DoubleExcitation",
        "DoubleExcitationPlus",
        "DoubleExcitationMinus",
        "QubitCarry",
        "QubitSum",
        "OrbitalRotation",
        "ECR",
        "BlockEncode",
        "C(BlockEncode)",
        "QFT",  # Add this line to include QFT
    }
)
  • add C++ tests for the QFT gate in the Test_GateImplementations_Nonparam.cpp file

Use PENNYLANE_RUN_TEST Macro, implement test function, and add test case
QFT(∣000⟩)= ​1/sqrt(8) * (∣000⟩+∣001⟩+∣010⟩+∣011⟩+∣100⟩+∣101⟩+∣110⟩+∣111⟩)
so each amplitude is approximately 0.353553

template <typename PrecisionT, class GateImplementation> void testApplyQFT() {
    using ComplexT = std::complex<PrecisionT>;
    const std::size_t num_qubits = 3;

    auto st = createZeroState<ComplexT>(num_qubits);
    GateImplementation::applyQFT(st.data(), num_qubits, {0, 1, 2}, false);

    // Expected result of QFT(|000>) for a 3-qubit system
    std::vector<ComplexT> expected_result = {
        ComplexT{0.353553, 0.0}, ComplexT{0.353553, 0.0}, ComplexT{0.353553, 0.0},
        ComplexT{0.353553, 0.0}, ComplexT{0.353553, 0.0}, ComplexT{0.353553, 0.0},
        ComplexT{0.353553, 0.0}, ComplexT{0.353553, 0.0}
    };

    CHECK(st == approx(expected_result).margin(1e-7));
}
PENNYLANE_RUN_TEST(QFT);
  • implement QFT
template <class PrecisionT>
    [[nodiscard]] static auto applyQFT(
        std::complex<PrecisionT> *arr, std::size_t num_qubits,
        const std::vector<std::size_t> &wires, bool inverse = false) {
        auto local_wires = wires;
        for (std::size_t i = 0; i < local_wires.size(); ++i) {
            applyHadamard(arr, num_qubits, {local_wires[i]}, inverse);
            for (std::size_t j = i + 1; j < local_wires.size(); ++j) {
                PrecisionT angle = M_PI / (1U << (j - i));
                if (inverse) {
                    angle = -angle;
                }
                applyControlledPhaseShift(arr, num_qubits, {local_wires[j], local_wires[i]}, inverse, angle);
            }
        }
        std::reverse(local_wires.begin(), local_wires.end());
    }
  • add pytest qft
@pytest.mark.parametrize("num_qubits", [2, 3, 4])
def test_qft_gate(num_qubits):
    dev = qml.device("lightning.qubit", wires=num_qubits)
    @qml.qnode(dev)
    def circuit():
        qml.QFT(wires=range(num_qubits))
        return qml.state()
    output_state = circuit()
    expected_matrix = qml.QFT.compute_matrix(num_qubits)
    initial_state = np.zeros((2**num_qubits,), dtype=np.complex128)
    initial_state[0] = 1
    expected_state = expected_matrix @ initial_state
    assert output_state.shape == expected_state.shape, "State dimensions mismatch"
    assert np.allclose(output_state, expected_state, atol=1e-6), "QFT state does not match expected state"

Fixes #975

@tomlqc
Copy link
Contributor

tomlqc commented Nov 11, 2024

@gzquse I just triggered the CI

Copy link

codecov bot commented Nov 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 38.94%. Comparing base (0419cdb) to head (649a2a0).
Report is 1 commits behind head on master.

❗ There is a different number of reports uploaded between BASE (0419cdb) and HEAD (649a2a0). Click for more details.

HEAD has 3 uploads less than BASE
Flag BASE (0419cdb) HEAD (649a2a0)
8 5
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #990       +/-   ##
===========================================
- Coverage   95.47%   38.94%   -56.53%     
===========================================
  Files         221       43      -178     
  Lines       33055     2963    -30092     
===========================================
- Hits        31559     1154    -30405     
- Misses       1496     1809      +313     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@gzquse
Copy link
Author

gzquse commented Nov 11, 2024

review required

@tomlqc
Copy link
Contributor

tomlqc commented Nov 11, 2024

Thanks @gzquse
Please link the PR to the issue and give the PR a name that describes the feature.
I was wondering about the benchmark script and results. Have I missed them?

@gzquse
Copy link
Author

gzquse commented Nov 11, 2024

Hi @tomlqc ,

You are right. I have some issues using a local apple chip to do the benchmark and I also tried to use our uni HPC to do the experiment but seems the CMake is not the same.

May I ask if it is possible to get a test environment from Pennylane's HPC? Therefore, I can test it a bit further.

@gzquse
Copy link
Author

gzquse commented Nov 11, 2024

Benchmark Jupyter notebook for reference

import pennylane as qml
from pennylane import numpy as np
import time

dev = qml.device("default.qubit", wires=10)

def benchmark_qft(num_qubits):
    results = {"num_qubits": [], "QubitUnitary_time": [], "qml_QFT_time": []}
    
    for n in range(1, num_qubits + 1):
        @qml.qnode(dev)
        def qft_with_qubit_unitary():
            qft_matrix = qml.QFT.compute_matrix(n)
            qml.QubitUnitary(qft_matrix, wires=range(n))
            return qml.state()
        
        @qml.qnode(dev)
        def qft_with_qml_gate():
            qml.QFT(wires=range(n))
            return qml.state()
       
        start_time = time.time()
        qft_with_qubit_unitary()
        unitary_time = time.time() - start_time

        start_time = time.time()
        qft_with_qml_gate()
        qml_gate_time = time.time() - start_time

        results["num_qubits"].append(n)
        results["QubitUnitary_time"].append(unitary_time)
        results["qml_QFT_time"].append(qml_gate_time)

    return results

num_qubits = 10  # Adjust based on desired range
benchmark_results = benchmark_qft(num_qubits)

print(benchmark_results)

import pandas as pd
results_df = pd.DataFrame(benchmark_results)

results_df.to_csv("qft_benchmark_results.csv", index=False)
print("Benchmark results saved to qft_benchmark_results.csv.")

@tomlqc
Copy link
Contributor

tomlqc commented Nov 12, 2024

Hi @gzquse,
You don't need to scale up to a very large number of qubits. Using your local laptop for this is perfectly fine. Please just show the results that you were able to generate. For this create some plots and share them with you analysis in this PR.
To share files or larger chunks of code, as e.g. the jupyter notebook, prefer using https://gist.github.com/, it will be rendered nicely.
Please also check again my previous message about link and title.
Thanks


@pytest.mark.parametrize("num_qubits", [2, 3, 4])
def test_qft_gate(num_qubits):
dev = qml.device("lightning.qubit", wires=num_qubits)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a note before we start the actual review. Please use default.qubit in this pytest to check the validity of the new implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the latest change test your implementation against default.qubit? This is what I meant.
You may want to take a look at the other tests in this file.

Copy link
Author

Choose a reason for hiding this comment

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

yes

@gzquse gzquse changed the title Solution Nov 8 Implement QFT in lightning.qubit and benchmark Nov 12, 2024
@gzquse gzquse changed the title Implement QFT in lightning.qubit and benchmark Implement QFT in lightning.qubit and benchmark (Fixes #975) Nov 12, 2024
@gzquse gzquse changed the title Implement QFT in lightning.qubit and benchmark (Fixes #975) Fixes #975: Implement QFT in lightning.qubit and benchmark Nov 12, 2024
@gzquse gzquse changed the title Fixes #975: Implement QFT in lightning.qubit and benchmark Fix issue #975 : Implement QFT in lightning.qubit and benchmark Nov 13, 2024
@gzquse
Copy link
Author

gzquse commented Nov 13, 2024

Benchmark QFT result

image

@gzquse gzquse requested a review from tomlqc November 13, 2024 02:34
@gzquse
Copy link
Author

gzquse commented Nov 13, 2024

Hi @tomlqc ,
Could you help me manually to link the issue?

Fix issue #975 : Implement QFT in lightning.qubit and benchmark

Thanks

@gzquse
Copy link
Author

gzquse commented Nov 13, 2024

Gist

script

@tomlqc
Copy link
Contributor

tomlqc commented Nov 13, 2024

Could you help me manually to link the issue?

https://www.google.com/search?q=how+to+link+github+pr+to+issue
Doesn't it work with using a keyword in the description?

@tomlqc
Copy link
Contributor

tomlqc commented Nov 13, 2024

Benchmark QFT result

Is your laptop already struggling with >10 qubits?

@gzquse gzquse changed the title Fix issue #975 : Implement QFT in lightning.qubit and benchmark Fixes #975 : Implement QFT in lightning.qubit and benchmark Nov 14, 2024
@gzquse
Copy link
Author

gzquse commented Nov 14, 2024

Hi @tomlqc,
As expected, I fired up 10 rounds and 14 qubits benchmark scripts, which burn 99% of the memory of my MacOs.
a 14-qubit QFT matrix is 2^14×2^14 > 4 GB in memory.
https://docs.google.com/spreadsheets/d/1Jj-uHv-e42XTPd2RgM6tSz6J7sOS5o1w8RbNINUED24/edit?usp=sharing

@gzquse
Copy link
Author

gzquse commented Nov 14, 2024

Could you help me manually to link the issue?

https://www.google.com/search?q=how+to+link+github+pr+to+issue Doesn't it work with using a keyword in the description?

I assume the original pr was under the forked branch. So the "Fix" does not work?

@tomlqc
Copy link
Contributor

tomlqc commented Nov 14, 2024

Thanks @gzquse for the update

a 14-qubit QFT matrix is 2^14×2^14 > 4 GB in memory

  • Is the data in the Google sheet of the same kind as in the "Benchmark QFT result" plot but just a different representation?
  • Does that mean that you would be able to extend the plot to 13 or even 14 qubit? If so, please update this plot.

@tomlqc
Copy link
Contributor

tomlqc commented Nov 14, 2024

So the "Fix" does not work?

I added the line to the description (not the title) and it worked fine.

@gzquse
Copy link
Author

gzquse commented Nov 14, 2024

So the "Fix" does not work?

I added the line to the description (not the title) and it worked fine.

Thanks for pointing out

@gzquse
Copy link
Author

gzquse commented Nov 14, 2024

Thanks @gzquse for the update

a 14-qubit QFT matrix is 2^14×2^14 > 4 GB in memory

  • Is the data in the Google sheet of the same kind as in the "Benchmark QFT result" plot but just a different representation?
  • Does that mean that you would be able to extend the plot to 13 or even 14 qubit? If so, please update this plot.

Hi,

  • Yes, it is the same representation.
  • I just put the 14-qubit benchmark on the right. In the 12-qubit plot on the left, you can see that I set the y-axis to a log scale. Up to 12 qubits, the two methods are relatively close in performance, alternating in speed. However, after 13 qubits, the new method shows a significant advantage in running time. I expect a similar trend on HPC systems.

@tomlqc
Copy link
Contributor

tomlqc commented Nov 14, 2024

Thanks @gzquse

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement QFT as a gate in Lightning-Qubit
3 participants