Skip to main content

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)