pasqal-io / Pulser

Library for pulse-level/analog control of neutral atom devices. Emulator with QuTiP.
Apache License 2.0
160 stars 57 forks source link

Global Rydberg pulse is only single threaded #177

Open paoloviviani opened 3 years ago

paoloviviani commented 3 years ago

I tried to run execute the UD-MIS tutorial either on the proposed register and on a larger one (10 quits) with longer sequences/more layers. With typical pulser/qutip installation from pip the single quantum loop runs always on a single core only.

I tried to:

but none of the solutions above improved the cpu usage beyond a single core.

In general it would be possible to parallelise the code at a different level (e.g. at the level of the optimization loop, like with population-based optimizers), however, I was wondering if this is the expected behaviour or if there is a chance of parallelising the simulation itself.

sebgrijalva commented 3 years ago

Hi Paolo,

Thanks for raising this issue, indeed, It's interesting to try to run a multi-thread job for Pulser leveraging QuTiP. I will try to reproduce a basic approach and get back to you. We might have to ask the devs at QuTiP too if necessary.

sebgrijalva commented 3 years ago

Here's a simple implementation of using multiple cores with Pulser and leveraging QuTiP's parallel_map() function:

import numpy as np
import qutip

from pulser import Register, Pulse, Sequence
from pulser.devices import Chadoq2
from pulser.simulation import Simulation

# Create a parametrized sequence
reg = Register.from_coordinates([[0,0]] )  # Just one atom.
seq = Sequence(reg, Chadoq2)
seq.declare_channel('ch0', 'rydberg_global')

rabi = seq.declare_variable('rabi')

seq.add(Pulse.ConstantPulse(200, rabi ,0, 0), 'ch0')

Notice that instead of assigning values one by one in a for loop (and thus having to redefine several times the Sequenceinstance), I am using a parametrized sequence with variable rabi. Then, I will build each sequence according to a given param:

# Define simulation experiment function:
def experiment(param):
    obs = qutip.basis(2,0).proj()  # Calculate occupation in the rydberg state
    seq_instance = seq.build(rabi=param)
    seq_instance.measure('ground-rydberg')

    sim = Simulation(seq_instance, sampling_rate=0.01)
    res = sim.run()

    return res.expect([obs])[0]

With this setup you can use QuTiP's parallel_map() function which automatically uses all the cores available in your computer :

qutip.parallel_map(experiment, np.linspace(1, 1.5, 100)) # Get expectation values for the entire dynamics and 100 parameter values

Notice this will be twice as fast as simply using

for val in np.linspace(1,1.5,1000):
    experiment(val)

Depending on the parameters of your sequence, this might be what you're looking for. Let me know if it helps.

HGSilveri commented 2 years ago

@sebgrijalva This issue has been open for a while now, what's the status on this? Should we close it / archive it?

sebgrijalva commented 2 years ago

I'd say we archive it, yes.