Send a job to Fresnel
Fresnel is Pasqal's state of the art neutral-atom QPU available on the cloud.
You can use the cloud SDK to send a job to Fresnel just like you did for an emulator in the previous section.
However there are a few hardware constraints to keep in mind before sending a job for execution on a real QPU. Some additional validation rules are checked on your sequence before it is scheduled:
- the channels defined in your sequence must match with channels available on the device
- your register must derive from a register layout
- the register must verify the constraints of the hardware: minimum spacing, atom count, etc.
Before sending a job to Fresnel we recommend that you first test your sequence by targeting an emulator backend. By default, the validation rules for real hardware are not applied, however you can enable strict validation rules using the optional strict_validation
attribute of the configuration for your batch. If your batch is successful when using this flag, it should be ready for execution on Fresnel.
Get the Fresnel specs
The specifications of our QPUs are accessible via the SDK which will let you build a pulser Device
object that you can use to design your pulser Sequence
.
from pulser.json.abstract_repr.deserializer import deserialize_device
from pasqal_cloud import SDK
project_id = "your_project_id"# Replace this value by your project_id on the PASQAL platform.
username = "your_username"# Replace this value by your username or email on the PASQAL platform.
# Initialize the cloud client
sdk = SDK(username=username, project_id=project_id)
specs = sdk.get_device_specs_dict()
# Build a device object to describe the specs of our "Fresnel" QPU
device = deserialize_device(specs["FRESNEL"])
Inspect the newly build device
object, this describes all the constraints of the hardware and will help you design your sequence. Refer to the pulser documentation on devices for more details.
Build your register
You need to define both a register and a layout before proceeding. The register specify where atoms will be arranged, while the layout specifies the positioning of traps necessary to capture and structure these atoms within the register.
For details on layouts, see the Pulser documentation.
Pre-calibrated layouts
The device defines a list of pre-calibrated layouts. You can build your register out of one of these layouts.
This is the recommended option because it will improve the performance of the QPU
-
Option 1: Define your register using pre-calibrated layouts:
Inspect the layouts available on Fresnel and define your register from this layout. Check the pulser documentation for more information on how to do that.
Example:
# let's say we are interested in the first layout available on the device
layout = device.pre_calibrated_layouts[0]
# Select traps 1, 3 and 5 of the layout to define the register
traps = [1,3,5]
register = layout.define_register(*traps)
# You can draw the resulting register to verify it matches your expectations
register.draw()
Arbitrary layouts
If pre-calibrated layouts do not satisfy the requirements of your experiment, you can create a custom layout.
For any given arbitrary register, a neutral-atom QPU will place traps according to the layout, which must then undergo calibration. Since each calibration requires time, it is generally advisable to reuse an existing calibrated layout whenever possible
-
Option 2: Automatically derive a layout from your defined register
This option allows for the automatic generation of a layout based on a specified register. However, for large registers, this process may yield sub-optimal solutions due to limitations in the algorithm used to create the layout.
qubits = {
"q0": (0, 0),
"q1": (0, 10),
"q2": (8, 2),
"q3": (1, 15),
"q4": (-10, -3),
"q5": (-8, 5),
}
register = Register(qubits).with_automatic_layout(device) -
Option 3: Define your register using a manually defined layout
- Create an arbitrary layout with 20 traps randomly positioned in a 2D plane
import numpy as np
# Generating random coordinates
np.random.seed(301122) # Keeps results consistent between runs
traps = np.random.randint(0, 30, size=(20, 2))
traps = traps - np.mean(traps, axis=0)
# Creating the layout
layout = RegisterLayout(traps, slug="random_20")- Define your register with specific trap IDs
trap_ids = [4, 8, 19, 0]
reg1 = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"])
reg1.draw()
Design your sequence
Once you have defined your register you can now create a Sequence
object using the Fresnel device and register. Refer to the pulser documentation to design your sequence.
# Assuming `device` is the pulser Device object
# And `register` is the register defined from one of the layout of the device
# Build a pulser sequence
seq = Sequence(register, device)
# Now design your sequence by defining variables, adding pulses, ...
Send the job for execution
Once you have designed your sequence, you can serialize it and send it for execution.
First send it to an emulator with the strict_validation
flag. If your job is successful and you are happy with the result, send it to Fresnel as follows.
from pasqal_cloud.device import EmulatorType, EmuFreeConfig, EmuTNConfig
# serialize your sequence
serialized_sequence = seq.to_abstract_repr()
# Create a batch of 1 job with 100 runs
jobs = [{"runs": 100}]
# First send it to an emulator for validation
configuration = EmuTNConfig(strict_validation=True)
batch = sdk.create_batch(
serialized_sequence, jobs, emulator=EmulatorType.EMU_TN, configuration=configuration
)
# Simply omit the `emulator` and `configuration` arguments to target Fresnel
fresnel_batch = sdk.create_batch(serialized_sequence, jobs)