A faster displacement operator avoiding matrix exponentiation
See original GitHub issueMatrix exponentiation is a costly operation. See [1]Nineteen Dubious Ways to Compute the Exponential of a Matrix, Twenty-Five Years Later∗.
In a quantum optics, the displacement operator is one of the most basic. It is used to create coherent states from vacuum and forms one of the two gates for universal control of a cavity (Displacement + SNAP gates) [2] Efficient cavity control with SNAP gates.
When we want to write an optimisation routine that finds best displacement parameters in a routine similar to the paper above [2], it would be nice if we can compute the operator faster without doing matrix exponentiation as qutip does now: https://github.com/qutip/qutip/blob/master/qutip/operators.py#L732
I have some notes from a colleague who calculated an analytical formula to compute the matrix elements of the displacement operator without having to do matrix exponentiation [3]: Displacement_operator.pdf
A PR to implement this in QuTiP would be great. We could first write a _displace_analytical
function that calculates the displacement matrix using the Scipy Laguerre polynomial and have it as an option as displace(N, alpha, offset, method=
analytical )
Could it also come in handy for optimal control? @ajgpitch
In the paper above [2], the authors use gradient descent to fine tune the parameters of a gate sequence containing displacement gates and SNAP gates to target some Bosonic quantum state.
We wish to do similar things for @araza6 s GSoC project.
Issue Analytics
- State:
- Created 3 years ago
- Comments:17 (17 by maintainers)
Top GitHub Comments
Ok, that makes sense to me. As long as you’re making its constructor public, it shouldn’t start with an underscore (i.e. just be
class Displacer
or whatever), but other than that, I can certainly go along with what you’re saying.I was just thinking about this again and came up with a good speed up for the truncated Hilbert space. I can’t think of any method to get analytic closed-form solutions for the truncated space, though, so this is just a more efficient numerical method.
First we take the generator of the displacement operator
G
, such thatexp(G)
is the displacement operator we’re looking for.G
is anti-Hermitian, and so it shares its eigensystem (up to scaling of the eigenvalues) with the Hermitiani G
and consequently is diagonalised by a unitary formed of its eigenvectors. NowS = i G / abs(alpha)
is a tridiagonal Hermitian, and with a similarity transformation we can find a real-symmetric tridiagonalT = P^-1 . S . P
for some diagonal unitaryP
(which is easy to calculate). The reason for scaling outalpha
here should become clear at the end.The main diagonal of
T
is all zeros, and the first sub- and super-diagonals look like[sqrt(1), -sqrt(2), sqrt(3), -sqrt(4), ...]
and the diagonal ofP
looks like[i, e^(-1i arg(alpha)), i e^(-2i arg(alpha)), e^(-3i arg(alpha)), ...]
Now this real-symmetric tridiagonal form is the basis of Hermitian eigenvalue solvers, and has direct entry points in LAPACK (e.g.
?stemr
), which allow us to pass only the main diagonal and the first subdiagonal. Scipy provides convenient wrapped access in Python byscipy.linalg.eigh_tridiagonal
. This lets us get the full eigensystem ofT
, which is related to that ofG
by dividing the eigenvalues by the scaling factor, and multiplying the eigenvectors byP
to transform them into the correct basis.We now have a diagonalised matrix
G = Q^-1 . D . Q
, soexp(G) = Q^-1 . exp(D) . Q
, which is now trivial becauseD
is diagonal.Putting all this together allows us to use our knowledge of the problem domain to convert the matrix exponentiation problem into a much simpler real-symmetric tridiagonal eigensystem problem, which gets us a nice big speed up, and it’s equivalent up to the tolerance of the eigenvalue solver (~1e-14).
Even better for you, a lot of the hard work is done in the eigensystem solver, and I scaled out
alpha
at the start, so we can do a good chunk without fixing alpha. That means we can pay the computational cost only once at the start, and then get faster calculations from then on.If I make a totally fair test, and simply replicate the full functionality of
qutip.displace
(including creating aQobj
at the end), my method is ~4x faster on small matrices (1 <= dim <= 20
) and it only goes up from there (I found it’s about ~10x faster atdim = 1000
, and beyond thatqutip.displace
is too slow to bother).If I store the calculation of the eigensystem, and output an
ndarray
instead of converting tocsr_matrix
(and so don’t produce aQobj
), then I find speed ups in getting the operator for a newalpha
as ~100x for small matrices and ~25x for large ones. The larger a matrix is, the more the computational time is dominated by the dense dot product at the end.Code:
While the analytic closed-form solution of the eigensystem is difficult, you may be able to express the eigenvalues as some function of the roots of a constructed orthogonal polynomial - you can create a recurrence relationship for the determinant of the characteristic equation of the system, and that typically ends up producing orthogonal polynomials. I didn’t pursue this very far because it looked difficult, and eventually you’d still need to calculate the eigenvectors anyway, which I didn’t have many ideas for. Unfortunately my copy of Numerical Methods is still in my office, and I can’t get to it!