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

Refactor QubitWaveFunction, add support for Numpy array backed dense representation #370

Merged
merged 4 commits into from
Nov 13, 2024

Conversation

ohuettenhofer
Copy link
Contributor

@ohuettenhofer ohuettenhofer commented Nov 11, 2024

When running simulate to get the output wavefunction of a circuit, currently the conversion from the array that the backend returns to the dictionary format that Tequila uses can be a bottleneck.
This pull request fixes this by refactoring the QubitWaveFunction class and allowing it to either use a dictionary for sparse wavefunctions, or a Numpy array for dense wavefunctions.

To see the performance improvement, consider the code from my previous pull request, but increase the number of qubits to 22:

Code
import tequila as tq
from math import pi
import time

SIZE = 22

def qft(size: int, inverse: bool = False) -> tq.QCircuit():
    U = tq.QCircuit()
    for i in range(size):
        U += tq.gates.H(target=i)
        for j in range(size - i - 1):
            U += tq.gates.Phase(target=i + j + 1, angle=(-1 if inverse else 1) * pi / 2 ** (j + 2))
            U += tq.gates.CRz(target=i, control=i + j + 1, angle=(-1 if inverse else 1) * pi / 2 ** (j + 1))
    return U

U = qft(SIZE)
V = qft(SIZE) + qft(SIZE, inverse=True)

start = time.time()
wfn = tq.simulate(U, backend="qulacs")
print(f"QFT time: {time.time() - start} s")

start = time.time()
wfn = tq.simulate(V, backend="qulacs")
print(f"QFT + inverse QFT time: {time.time() - start} s")

On the current devel branch, this takes around 10 seconds for only the QFT, and around 2.4 seconds for the QFT combined with the inverse. With this pull request, this is reduced to around 0.95s / 1.85s. Not only is it faster, but the larger circuit is now slower, unlike before where the larger circuit was faster because the result was a sparse wavefunction. The time spent in the Qulacs update_quantum_state method is around 0.7s / 1.4s, so now the simulation actually makes up the majority of the runtime.

In theory, both sparse and dense representations should be functionally equivalent, and the only difference should be a space-time tradeoff.
Most of the time, the sparse representation is used by default to preserve the previous behaviour.
The dense representation is used when the wavefunction is constructed from an array, or when it is chosen explicitly.

I also made various other changes to the wavefunction API.

@ohuettenhofer ohuettenhofer marked this pull request as ready for review November 12, 2024 16:30
wfn = simulate(U)
wfnx = simulate(X(2))
assert numpy.isclose(numpy.abs(wfn.inner(wfnx))**2,1.0)

U = X(2)
U += SWAP(0,2, power=2.0)
U += SWAP(0,2, power=3.0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't understand the test the way it was written before. SWAP^2 = I, so this simply expects X(2) to have the same output as X(0)? In comparison, a few lines later, a SWAP with power 2 is tested in a context where it makes sense to not actually swap.

I'm not sure, but I assumed that here we actually want to execute a swap, while testing the power argument, so I changed it to 3.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes, I'm confused as well. Makes more sense like this

@kottmanj kottmanj merged commit 57d6b89 into tequilahub:devel Nov 13, 2024
11 checks passed
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.

2 participants