Skip to content
Pasqal Documentation

Tutorial 1 (low-level variant): using a Quantum Device to extract machine-learning features

As in any machine learning task, we first need to load and prepare data. QEK can work with many types of graphs, including molecular graphs. For this tutorial, we will use the PTC-FM dataset, which contains such molecular graphs.

# Load the original PTC-FM dataset
import torch_geometric.datasets as pyg_dataset
from qek.shared.retrier import PygRetrier
# We use PygRetrier to retry the download if it fails.
og_ptcfm = PygRetrier().insist(pyg_dataset.TUDataset, root="dataset", name="PTC_FM")
display("Loaded %s samples" % (len(og_ptcfm), ))
from tqdm import tqdm
import pulser as pl
import qek.data.graphs as qek_graphs
graphs_to_compile = []
for i, data in enumerate(tqdm(og_ptcfm)):
graph = qek_graphs.PTCFMGraph(data=data, device=pl.AnalogDevice, id=i)
graphs_to_compile.append(graph)

Once the embedding is found, we compile a Register (the position of atoms on the Quantum Device) and a Pulse (the lasers applied to these atoms).

Note that not all graphs can be embedded on a given device. In this notebook, for the sake of simplicity, we simply discard graphs that cannot be trivially embedded. Future versions of this library may succeed at embedding more graphs.

from qek.shared.error import CompilationError
compiled = []
for graph in tqdm(graphs_to_compile):
try:
register = graph.compile_register()
pulse = graph.compile_pulse()
except CompilationError:
# Let's just skip graphs that cannot be computed.
print("Graph %s cannot be compiled for this device" % (graph.id, ))
continue
compiled.append((graph, register, pulse))
print("Compiled %s graphs into registers/pulses" % (len(compiled, )))
example_graph, example_register, example_pulse = compiled[64]
# The molecule, as laid out on the Quantum Device.
example_register.draw()
# The laser pulse used to control its state evolution.
example_pulse.draw()

You may experiment with different pulses, by passing arguments to compile_pulse.

example_pulse = graphs_to_compile[0].compile_pulse(normalized_amplitude=0.1, normalized_duration=0.1) # arbitrary values
example_pulse.draw()

Executing the compiled graphs on an emulator

Section titled “Executing the compiled graphs on an emulator”

While our objective is to run the compiled graphs on a physical QPU, it is generally a good idea to test out some of these compiled graphs on an emulator first. For this example, we'll use the QutipEmulator, the simplest emulator provided with Pulser.

from qek.data.processed_data import ProcessedData
from qek.target.backends import QutipBackend
# In this tutorial, to make things faster, we'll only run the graphs that require 5 qubits or less.
# If you wish to run more entries, feel free to increase this value.
#
# # Warning
#
# Emulating a Quantum Device takes exponential amount of resources and time! If you set MAX_QUBITS too
# high, you can bring your computer to its knees and/or crash this notebook.
MAX_QUBITS = 5
processed_dataset = []
backend = QutipBackend(device=pl.AnalogDevice)
for graph, register, pulse in tqdm(compiled):
if len(register) > MAX_QUBITS:
continue
states = await backend.run(register=register, pulse=pulse)
processed_dataset.append(ProcessedData.custom(register=register, pulse=pulse, device=pl.AnalogDevice, state_dict=states, target=graph.target))

Once you have checked that the compiled graphs work on an emulator, you will probably want to move to a QPU. Execution on a QPU takes resources polynomial in the number of qubits, which hopefully means an almost exponential speedup for large number of qubits.

To experiment with a QPU, you will need either physical access to a QPU, or an account with PASQAL Cloud (external), which provides you remote access to QPUs built and hosted by Pasqal. In this section, we'll see how to use the latter.

If you don't have an account, just skip to the next section!

HAVE_PASQAL_ACCOUNT = False # If you have a PASQAL Cloud account, fill in the details and set this to `True`.
if HAVE_PASQAL_ACCOUNT:
from qek.target.backends import RemoteQPUBackend
processed_dataset = []
# Initialize connection
my_project_id = "your_project_id"# Replace this value with your project_id on the PASQAL platform.
my_username = "your_username" # Replace this value with your username or email on the PASQAL platform.
my_password = "your_password" # Replace this value with your password on the PASQAL platform.
# Security note: In real life, you probably don't want to write your password in the code.
# See the documentation of PASQAL Cloud for other ways to provide your password.
# Initialize the cloud client
backend = RemoteQPUBackend(username=my_username, project_id=my_project_id, password=my_password)
# Fetch the specification of our QPU
device = await backend.device()
# As previously, create the list of graphs and embed them.
graphs_to_compile = []
for i, data in enumerate(tqdm(og_ptcfm)):
graph = qek_graphs.PTCFMGraph(data=data, device=device, id=i)
graphs_to_compile.append(graph)
compiled = []
for graph in tqdm(graphs_to_compile):
try:
register = graph.compile_register()
pulse = graph.compile_pulse()
except CompilationError:
# Let's just skip graphs that cannot be computed.
print("Graph %s cannot be compiled for this device" % (graph.id, ))
continue
compiled.append((graph, register, pulse))
# Now that the connection is initialized, we just have to send the work
# to the QPU and wait for the results.
for graph, register, pulse in tqdm(compiled):
# Send the work to the QPU and await the result
states = await backend.run(register=register, pulse=pulse)
processed_dataset.append(ProcessedData.custom(register=register, pulse=pulse, device=device, state_dict=states, target=graph.target))

For this notebook, instead of spending hours running the simulator on your computer, we're going to skip this step and load on we're going to cheat and load the results, which are conveniently stored in ptcfm_processed_dataset.json.

import qek.data.processed_data as qek_dataset
processed_dataset = qek_dataset.load_dataset(file_path="ptcfm_processed_dataset.json")
print(f"Size of the quantum compatible dataset = {len(processed_dataset)}")
# The geometry we compiled from this graph for execution on the Quantum Device.
dataset_example: ProcessedData = processed_dataset[64]
dataset_example.draw_register()
# The laser pulses we used to drive the execution on the Quantum Device.
dataset_example.draw_pulse()
display(dataset_example.state_dict)
print(f"Total number of samples: {sum(dataset_example.state_dict.values())}")

From the state dictionary, we derive as machine-learning feature the distribution of excitation. We'll use this in the next notebook to define our kernel.

dataset_example.draw_excitation()

What we have seen so far covers the use of a Quantum Device to extract machine-learning features.

For the next step, we'll see how to use these features for machine learning.