Skip to content

Optimizing quantum analysis #36

Description

@DanielCohenHillel

TL;DR: This program should've been written on a quantum computer... My PC really starting to straggle:\

After converting the code from using qutip to using numpy (see remove_qutip branch) I started playing around with it. One thing I noticed is takes an extremely long time to run for many modes. Just as an example, on the computer in our lab, running epra.analyze_all_variations(cos_trunc=8, fock_trunc=15) takes:

  • for 2 modes: 3 seconds
  • for 3 modes: 15 seconds
  • for 4 modes: Literally so long I never saw it finish (more than 20 mins, after that I stopped it). Moreover, qutip crushed due to memory problems.

This could make the code impractical for some complex applications (I don't really know if such applications exist, so it might be a non issue). This is when I got into the optimization rabbit hole. I'm posting here some of what I found so other people can look into it and try to optimize it, I think I'll do so in a few days. I believe that in a couple hours work a 10x-20x improvement is very possible (maybe even much more).

First thing, where that time is distributed (for 3 modes).

  • Basically all (99.5%) of the time is spent in the function epr_numerical_diagonalization in back_box_numeric.py, of which:
    • 30% of the time in the function black_box_hamiltonian of which:
      • Almost 100% of the time is spent on a single line! nonlinear_part = dot(-fjs, map(cos, cos_interiors))
    • 70% of the time in the function make_disspersive, I have not looked further into that function because for 4 modes, black_box_hamiltonian takes so long I never got past it.

A single line takes tens of minutes to complete and it's almost half of the total time , so I'll explain it a bit here.
The dot() is referring to MatrixOps.dot which is an implementation of the dot product that uses no external libraries. My guess is that it was done this way instead of with np.dot or something is that it was a way of dealing with the weird shapes (list of qobj etc...).

fjs is just a number defined few lines up. cos is a function defined a few lines up, it uses MatrixOps.cos_approx to approximate the cosine of cos_interiors. This line is equivalent to nonlinear_part = -fjs * cos( cos_interiors ) where cos is not np.cos but a cosine tylor approximation.

If anyone wants to give it a go, I'd start by replacing every list with a ndarray. Right now there are many lists of numpy matrices, where all matrices have the same shape, so it is pretty inefficient to store them as a list instead of a 3d ndarray. This would also make it easy since it would allow to use numpy functions instead of loops, so you can just write cos( cos_interiors ) for example and that would work as expected (cos should be defined a bit differently though).

Note: I was writing this with hopes that after some optimization we could achieve the ability to do analysis on 5,6 even 7 modes. But as I was writing this I did some checks and found that because of memory constraints, 4 modes is probably the limit we can do. The memory usage is proportional to fock_levels^2*num_modes, for 4 modes and 15 fock levels that's around 19GB! I do believe 4 modes is possible in reasonable time with the right optimization.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions