Skip to content

Commit 387e686

Browse files
authored
Show pulses and registers, hide sequences
For pedagogical reasons, we want to show pulses and registers (which will teach our users something about our architecture) but not pulser.Sequence (which is more of an API detail), so this reworks the API and tutorial to make it possible.
1 parent 0c3aa31 commit 387e686

11 files changed

Lines changed: 412 additions & 137 deletions

examples/tutorial 1 - Using a Quantum Device to Extract Machine-Learning Features.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@
232232
"cell_type": "markdown",
233233
"metadata": {},
234234
"source": [
235-
"We can check the sequence for one of the samples:"
235+
"We can check the geometry for one of the samples:"
236236
]
237237
},
238238
{
@@ -242,7 +242,7 @@
242242
"outputs": [],
243243
"source": [
244244
"dataset_example = processed_dataset[64]\n",
245-
"dataset_example.draw_sequence()"
245+
"dataset_example.draw_register()"
246246
]
247247
},
248248
{
@@ -251,7 +251,7 @@
251251
"metadata": {},
252252
"outputs": [],
253253
"source": [
254-
"dataset_example.draw_register()"
254+
"dataset_example.draw_pulse()"
255255
]
256256
},
257257
{

examples/tutorial 1a - Using a Quantum Device to Extract Machine-Learning Features - low-level.ipynb

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@
8484
"cell_type": "markdown",
8585
"metadata": {},
8686
"source": [
87-
"## Create a Pulser sequence\n",
87+
"## Compile a Register and a Pulse\n",
8888
"\n",
89-
"Once the embedding is found, we create a Pulser Sequence that can be interpreted by a Quantum Device.\n",
89+
"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).\n",
9090
"\n",
91-
"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."
91+
"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."
9292
]
9393
},
9494
{
@@ -97,26 +97,27 @@
9797
"metadata": {},
9898
"outputs": [],
9999
"source": [
100+
"from qek.shared.error import CompilationError\n",
101+
"\n",
100102
"compiled = [] \n",
101103
"\n",
102104
"for graph in tqdm(graphs_to_compile):\n",
103-
" sequence = None\n",
104105
" try:\n",
105-
" sequence = graph.compute_sequence()\n",
106-
" except ValueError:\n",
106+
" register = graph.compile_register()\n",
107+
" pulse = graph.compile_pulse()\n",
108+
" except CompilationError:\n",
107109
" # Let's just skip graphs that cannot be computed.\n",
108-
" print(\"Sequence %s cannot be compiled for this device\" % (graph.id, ))\n",
110+
" print(\"Graph %s cannot be compiled for this device\" % (graph.id, ))\n",
109111
" continue\n",
110-
" if sequence is not None:\n",
111-
" compiled.append((graph, sequence))\n",
112-
"print(\"Compiled %s sequences\" % (len(compiled, )))"
112+
" compiled.append((graph, register, pulse))\n",
113+
"print(\"Compiled %s graphs into registers/pulses\" % (len(compiled, )))"
113114
]
114115
},
115116
{
116117
"cell_type": "markdown",
117118
"metadata": {},
118119
"source": [
119-
"Let's take a look at some of these sequences."
120+
"Let's take a look at some of these registers and pulses."
120121
]
121122
},
122123
{
@@ -125,13 +126,13 @@
125126
"metadata": {},
126127
"outputs": [],
127128
"source": [
128-
"example_graph, example_sequence = compiled[64]\n",
129+
"example_graph, example_register, example_pulse = compiled[64]\n",
129130
"\n",
130131
"# The molecule, as laid out on the Quantum Device.\n",
131-
"example_sequence.register.draw(blockade_radius=pl.AnalogDevice.min_atom_distance + 0.01)\n",
132+
"example_register.draw(blockade_radius=pl.AnalogDevice.min_atom_distance + 0.01)\n",
132133
"\n",
133134
"# The laser pulse used to control its state evolution.\n",
134-
"example_sequence.draw()"
135+
"example_pulse.draw()"
135136
]
136137
},
137138
{
@@ -151,10 +152,15 @@
151152
"source": [
152153
"import pulser\n",
153154
"\n",
154-
"alternative_sequence = pulser.Sequence( # A sequence of 0 pulses.\n",
155-
" pulser.Register({\"q0\": (0, 0)}), # A single atom, called q0, at the center of the device.\n",
156-
" pulser.AnalogDevice # A generic analog quantum computer.\n",
157-
")\n"
155+
"example_register = pulser.Register({\"q0\": (0, 0)})\n",
156+
"example_pulse = pulser.Pulse.ArbitraryPhase(\n",
157+
" amplitude=pulser.waveforms.RampWaveform(duration=150, start=100, stop=300),\n",
158+
" phase=pulser.waveforms.ConstantWaveform(duration=150, value=15),\n",
159+
" post_phase_shift=5\n",
160+
")\n",
161+
"\n",
162+
"example_register.draw()\n",
163+
"example_pulse.draw()"
158164
]
159165
},
160166
{
@@ -180,8 +186,8 @@
180186
"metadata": {},
181187
"outputs": [],
182188
"source": [
183-
"from pulser_simulation import QutipEmulator\n",
184189
"from qek.data.dataset import ProcessedData\n",
190+
"from qek.backends import QutipBackend\n",
185191
"\n",
186192
"# In this tutorial, to make things faster, we'll only run the sequences that require 5 qubits or less.\n",
187193
"# If you wish to run more entries, feel free to increase this value.\n",
@@ -193,12 +199,12 @@
193199
"MAX_QUBITS = 5\n",
194200
"\n",
195201
"processed_dataset = []\n",
196-
"for graph, sequence in tqdm(compiled):\n",
197-
" if len(sequence.qubit_info) > MAX_QUBITS:\n",
202+
"executor = QutipBackend(device=pl.AnalogDevice)\n",
203+
"for graph, register, pulse in tqdm(compiled):\n",
204+
" if len(register.qubits) > MAX_QUBITS:\n",
198205
" continue\n",
199-
" simulator = QutipEmulator.from_sequence(sequence=sequence)\n",
200-
" states = simulator.run().sample_final_state()\n",
201-
" processed_dataset.append(ProcessedData(sequence=sequence, state_dict=states, target=graph.target))"
206+
" states = await executor.run(register=register, pulse=pulse)\n",
207+
" processed_dataset.append(ProcessedData.from_register(register=register, pulse=pulse, device=pl.AnalogDevice, state_dict=states, target=graph.target))"
202208
]
203209
},
204210
{
@@ -233,11 +239,10 @@
233239
"HAVE_PASQAL_ACCOUNT = False # If you have a PASQAL Cloud account, fill in the details and set this to `True`.\n",
234240
"\n",
235241
"if HAVE_PASQAL_ACCOUNT: \n",
242+
" from qek.backends import RemoteQPUBackend\n",
236243
" processed_dataset = []\n",
237244
"\n",
238245
" # Initialize connection\n",
239-
" from pulser.json.abstract_repr.deserializer import deserialize_device\n",
240-
" from pasqal_cloud import SDK\n",
241246
"\n",
242247
" my_project_id = \"your_project_id\"# Replace this value with your project_id on the PASQAL platform.\n",
243248
" my_username = \"your_username\" # Replace this value with your username or email on the PASQAL platform.\n",
@@ -246,62 +251,36 @@
246251
" # See the documentation of PASQAL Cloud for other ways to provide your password.\n",
247252
"\n",
248253
" # Initialize the cloud client\n",
249-
" sdk = SDK(username=my_username, project_id=my_project_id, password=my_password)\n",
254+
" executor = RemoteQPUBackend(username=my_username, project_id=my_project_id, password=my_password)\n",
250255
"\n",
251-
" # Fetch the latest lists of QPUs\n",
252-
" specs = sdk.get_device_specs_dict()\n",
253-
" # We'll use \"Fresnel\", generally the recommended QPU on PASQAL Cloud as of this writing.\n",
254-
" device = deserialize_device(specs[\"FRESNEL\"])\n",
256+
" # Fetch the specification of our QPU\n",
257+
" device = await executor.device()\n",
255258
"\n",
256259
" # As previously, create the list of graphs and embed them.\n",
257260
" graphs_to_compile = []\n",
258261
" for i, data in enumerate(tqdm(og_ptcfm)):\n",
259-
" graph = qek_graphs.MoleculeGraph(data=data, device=device, id=i)\n",
262+
" graph = qek_graphs.PTCFMGraph(data=data, device=device, id=i)\n",
260263
" graphs_to_compile.append(graph)\n",
261264
"\n",
262265
" compiled = []\n",
263266
" for graph in tqdm(graphs_to_compile):\n",
264267
" sequence = None\n",
265268
" try:\n",
266-
" sequence = graph.compute_sequence()\n",
267-
" except ValueError:\n",
269+
" register = graph.compile_register()\n",
270+
" pulse = graph.compile_pulse()\n",
271+
" except CompilationError:\n",
268272
" # Let's just skip graphs that cannot be computed.\n",
269273
" print(\"Sequence %s cannot be compiled for this device\" % (graph.id, ))\n",
270274
" continue\n",
271-
" if sequence is not None:\n",
272-
" compiled.append((graph, sequence))\n",
275+
" compiled.append((graph, register, pulse))\n",
273276
"\n",
274277
" # Now that the connection is initialized, we just have to send the work\n",
275278
" # to the QPU and wait for the results.\n",
276-
" for graph, sequence in tqdm(compiled):\n",
277-
"\n",
278-
" # Send the work to the QPU.\n",
279-
" batch = sdk.create_batch(\n",
280-
" # The sequence.\n",
281-
" sequence.to_abstract_repr(),\n",
282-
"\n",
283-
" # Run each sequence 1000 times to refine results. Recall that quantum computations\n",
284-
" # are probabilistic, so you need to run each sequence many times to progressively\n",
285-
" # refine your probability distribution.\n",
286-
" jobs=[{\"runs\": 1000}],\n",
287-
"\n",
288-
" # And wait for the results.\n",
289-
" #\n",
290-
" # WARNING\n",
291-
" #\n",
292-
" # # Wait lines\n",
293-
" #\n",
294-
" # As of this writing, the waiting line to access a QPU can be very long (typically\n",
295-
" # several hours). Argument `wait=True` will stop your program until the batch has\n",
296-
" # completed. You will have to determine whether that's what you want.\n",
297-
" wait=True,\n",
298-
" )\n",
299-
"\n",
300-
" # The sdk returns a single job.\n",
301-
" job = batch.jobs[0]\n",
302-
" assert job.status == \"DONE\"\n",
303-
" states = job.result\n",
304-
" processed_dataset.append(ProcessedData(sequence=sequence, state_dict=states, target=graph.target))"
279+
" for graph, register, pulse in tqdm(compiled):\n",
280+
"\n",
281+
" # Send the work to the QPU and await the result\n",
282+
" states = await executor.run(register=register, pulse=pulse)\n",
283+
" processed_dataset.append(ProcessedData.from_register(register=register, pulse=pulse, device=device, state_dict=states, target=graph.target))"
305284
]
306285
},
307286
{
@@ -345,7 +324,7 @@
345324
"cell_type": "markdown",
346325
"metadata": {},
347326
"source": [
348-
"We can check the sequence for one of the samples:"
327+
"Let's take a look at one of our samples:"
349328
]
350329
},
351330
{
@@ -354,8 +333,11 @@
354333
"metadata": {},
355334
"outputs": [],
356335
"source": [
357-
"dataset_example = processed_dataset[64]\n",
358-
"dataset_example.draw_sequence()"
336+
"from qek.data.dataset import ProcessedData\n",
337+
"\n",
338+
"# The geometry we compiled from this graph for execution on the Quantum Device.\n",
339+
"dataset_example: ProcessedData = processed_dataset[64]\n",
340+
"dataset_example.draw_register()"
359341
]
360342
},
361343
{
@@ -364,7 +346,8 @@
364346
"metadata": {},
365347
"outputs": [],
366348
"source": [
367-
"dataset_example.draw_register()"
349+
"# The laser pulses we used to drive the execution on the Quantum Device.\n",
350+
"dataset_example.draw_pulse()"
368351
]
369352
},
370353
{

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [
3838
"torch_geometric",
3939
"matplotlib",
4040
"emu-mps~=1.2.0",
41+
"pasqal-cloud",
4142
]
4243

4344
[tool.hatch.metadata]

0 commit comments

Comments
 (0)