Remote computing with Perceval

Here we aim at showing how to connect to Quandela’s cloud services to perform computation with real QPU and simulators remotely. We are going to use a simple two modes circuit here.

[1]:
import time
import numpy as np
from tqdm.notebook import tqdm

import perceval as pcvl
from perceval.algorithm import Sampler
from perceval.serialization import deserialize

First, define your objects through Perceval as usual.

[2]:
input_state = pcvl.BasicState([1, 1])

c = pcvl.Circuit(2)
c.add(0, pcvl.BS())
c.add(0, pcvl.PS(phi = np.pi/4))
c.add(0, pcvl.BS())

pcvl.pdisplay(c)
[2]:
../_images/notebooks_Remote_computing_4_0.svg

Now, visit cloud.quandela.com and login to see which QPU and simulators are available, as well as their specifications. Once you have chosen, all you have to do is to copy the machine’s name. You can now define a RemoteProcessor using the name of the machine and your token. Before using your token, don’t forget to give it the right to be used on the machine you want.

[3]:
# Use your key here to let the system know who you are
token_qcloud = 'YOUR_API_KEY'
remote_simulator = pcvl.RemoteProcessor("sim:ascella", token_qcloud)

You can now access to the specificities of the machine directly in Perceval.

[4]:
specs = remote_simulator.specs
pcvl.pdisplay(specs["specific_circuit"])
[4]:
../_images/notebooks_Remote_computing_8_0.svg
[5]:
print(specs["constraints"])
print(specs["parameters"])
{'max_mode_count': 12, 'max_photon_count': 4, 'min_mode_count': 2, 'min_photon_count': 1}
{'HOM': 'indistinguishability value, using HOM model (default 1)', 'backend_name': 'name of the backend that will be used for computation (default "SLOS")', 'final_mode_number': 'number of modes of the output states. states having a photon on unused modes will be ignored. Useful when using computed circuits (default input_state.m)', 'g2': 'g2 value (default 0)', 'mode_post_select': 'number of required detected modes to keep a state. (default input_state.n)', 'phase_imprecision': 'imprecision on the phase shifter phases (default 0)', 'transmittance': 'probability at each pulse that a photon is sent to the system and is detected (default 1)'}

Now we have to specify what parameters we want to give to compute. For specific parameters, we have to use a special set_parameter function (or set_parameters).

[6]:
remote_simulator.set_circuit(c)
remote_simulator.with_input(input_state)

remote_simulator.set_parameters({
    "HOM": .95,
    "transmittance": .1,
    "g2": .01
})
remote_simulator.mode_post_selection(1)  # equivalent to remote_simulator.set_parameter("mode_post_select", 1)

We can now use the Sampler with our RemoteProcessor.

[7]:
sampler = Sampler(remote_simulator)

nsample = 10000
async_job = sampler.sample_count.execute_async(nsample)

The order has now been sent to a distant computer. As it is an async computation, we can do other things locally before the results arrive. In our case, we will just wait for the end of the computation. If you go to the cloud website again, you could see the job and its completion status.

[8]:
previous_prog = 0
with tqdm(total=1, bar_format='{desc}{percentage:3.0f}%|{bar}|') as tq:
    tq.set_description(f'Get {nsample} samples from {remote_simulator.name}')
    while not async_job.is_complete:
        tq.update(async_job.status.progress/100-previous_prog)
        previous_prog = async_job.status.progress/100
        time.sleep(1)
    tq.update(1-previous_prog)
    tq.close()

print(f"Job status = {async_job.status()}")
Job status = SUCCESS

Once the previous cell has stopped, the job is finished (again, you can see its status on the website). We can now retrieve the results to do some computation. Here, the computation should be relatively fast (unless the simulator is unavailable or there are many requests on it), so we can use the job object we created before. If the computation lasted for a long time, we could have created a new job object and directly retrieved the result given the job id that is visible on the website.

[9]:
''' # To retrieve your job using a job id
remote_processor = pcvl.RemoteProcessor("sim:ascella", token_qcloud)
async_job = remote_processor.resume_job(id)
'''

results = async_job.get_results()
print(results['results'])
{
  |1,0>: 4829
  |1,1>: 261
  |0,1>: 4910
}

You can run the same sampling on the corresponding QPU:

[10]:
remote_qpu = pcvl.RemoteProcessor("qpu:ascella", token_qcloud)
remote_qpu.set_circuit(c)
remote_qpu.with_input(input_state)
remote_qpu.mode_post_selection(1)

sampler_on_qpu = Sampler(remote_qpu)

nsample = 10000
async_job = sampler_on_qpu.sample_count.execute_async(nsample)
[11]:
previous_prog = 0
with tqdm(total=1, bar_format='{desc}{percentage:3.0f}%|{bar}|') as tq:
    tq.set_description(f'Get {nsample} samples from {remote_qpu.name}')
    while not async_job.is_complete:
        tq.update(async_job.status.progress/100-previous_prog)
        previous_prog = async_job.status.progress/100
        time.sleep(1)
    tq.update(1-previous_prog)
    tq.close()

print(f"Job status = {async_job.status()}")
Job status = SUCCESS
[12]:
results = async_job.get_results()
print(results['results'])
{
  |0,1>: 13979
  |1,0>: 9627
  |1,1>: 51
}