Execution
In this page, you will learn how to:
- run a
QuantumProgramon local/remote emulators and QPUs. - inspect and extract the execution results
- choose between different emulator backends and configuration options.
- use a
connectionto run programs remotely through your provider. - submit remote jobs, query their status and retrieve results
As anticipated, a QuantumProgram can be run on multiple backends provided by Pasqal (external):
- locally installed emulators
- remote cloud emulators
- QPUs
Remote emulators and QPU require credentials to submit a job. More information on how to access a QPU through your favorite cloud provider, is available at Pasqal's website (external). Later, we will briefly show how to authenticate and send a remote job.
Let us revisit the quantum program definition described before in the Defining a quantum program page.
from qoolqit import AnalogDevice, Constant, Drive, QuantumProgram, Ramp, Register
# Create the registerregister = Register.from_coordinates([(0,1), (0,-1), (2,0)])
# Defining the drive parametersomega = 0.8delta_i = -2.0 * omegadelta_f = -delta_iT = 25.0
# Defining the drivewf_amp = Constant(T, omega)wf_det = Ramp(T, delta_i, delta_f)drive = Drive(amplitude = wf_amp, detuning = wf_det)
# Creating the programprogram = QuantumProgram(register, drive)
# Compiling the program to a deviceprogram.compile_to(device=AnalogDevice())In the following sections we will provide more details about local/remote emulation and QPU execution of your program.
Local Emulator
Section titled “Local Emulator”Local emulators run quantum simulations directly on your machine. They are ideal for development, testing, and small-scale quantum programs.
QoolQit provides easy access to local emulation through the LocalEmulator class, which allows you to run quantum programs locally.
For all emulators, a job handler is returned, to which you can query the results. Emulating locally will only return completed jobs, as opposed to remote emulators, which we will discuss later.
from qoolqit.execution import LocalEmulator
emulator = LocalEmulator()job = emulator.run(program)results = job.results()While the snippet above provides the basic way to run a quantum program, we will now discuss how to:
- select different emulator backends.
- get the results.
- configure emulation backends.
Backend Types
Section titled “Backend Types”The LocalEmulator allows to emulate the program run on different backends provided by Pasqal:
QutipBackendV2: Based on Qutip, runs programs with up to ~12 qubits and return qutip objects in the results (default).SVBackend: PyTorch based state vectors and sparse matrices emulator. Runs programs with up to ~25 qubits and return torch objects in the results. Requires installing theemu-svpackage (see the emu-sv documentation (external))MPSBackend: PyTorch based emulator using Matrix Product States (MPS). Runs programs with up to ~80 qubits and return torch objects in the results. Requires installing theemu-mpspackage (see the emu-mps documentation (external)).
To use a particular backend it is sufficient to specify it through the backend_type argument:
from qoolqit.execution import BackendType, LocalEmulator
emulator = LocalEmulator(backend_type=BackendType.QutipBackendV2)Handling local results
Section titled “Handling local results”The call emulator.run(program) will return a Job object type.
All execution methods (LocalEmulator.run(), RemoteEmulator.run(), and QPU.run()) return a Job object that serves as a handler for managing the execution lifecycle and to fetch the results once the computation is completed. More about this in the remote emulator section of this page.
As an example, lets inspect the results we got in the previous run:
# single result in the sequenceresults.get_result_tags()['bitstrings']
Then the bitstrings can be extracted simply as:
# single result in the sequencefinal_bitstrings = results.final_bitstringsfinal_bitstringsCounter({'111': 827,
'101': 60,
'011': 54,
'110': 42,
'010': 5,
'001': 5,
'000': 4,
'100': 3})
Emulation configuration
Section titled “Emulation configuration”Here we discuss how to fully configure emulator backends to exploit all their possibilities.
Notable examples include asking for specific observables, modifying the default evaluation times, emulating real hardware modulation effects, defining the initial state, setting noise models, etc.
This is done through the EmulationConfig object, which can be used to configure all emulators.
Once instantiated, the configuration can be then passed to the emulator at instantiation:
from qoolqit.execution import BitStrings, EmulationConfig, LocalEmulator, Occupation
# observables to compute, occupation of the Rydberg state and bitstrings sampleobservables = (Occupation(evaluation_times=[0.1, 0.5, 1.0]), BitStrings(num_shots=100))emulation_config = EmulationConfig( observables=observables, with_modulation=True )
# emulator with a custom configurationemulator = LocalEmulator(emulation_config=emulation_config)# run the program with the new configjob = emulator.run(program)As before, results can be inspected and queried specific observable result:
# get the resultsresults = job.results()
# get the result tags (this will include the observable names)print(results.get_result_tags())
# get the result for the occupation observable at time 1.0 (final time)results.get_result("occupation", time=1.0)['occupation', 'bitstrings']
[0.9475510886440889, 0.9475510886440889, 0.9473207271168341]
The key parameters of EmulationConfig are:
| Parameter | Description |
|---|---|
observables |
Sequence of observables to compute during emulation. The list of available observables is available here (external). |
default_evaluation_times |
Default relative times (between 0 and 1) at which observables are evaluated, or "Full" for every emulation step. Defaults to (1.0,) (end of simulation only). |
with_modulation |
Whether to emulate finite-bandwidth hardware modulation of the drive. |
initial_state |
Custom initial state (defaults to all qubits in the ground state). |
noise_model |
Optional noise model to apply during emulation. |
For a more detailed description of all configuration options, please refer to the API documentation of EmulationConfig here or directly from the Pulser documentation (external).
The EmulationConfig object is designed to be easily extensible, allowing for the addition of new configuration parameters or specific backend-specific configurations, respectively QutipConfig, SVConfig and MPSConfig. They should be used by pairing them with the corresponding backend type
and imported from their respective packages, namely pulser-simulation, emu-sv and emu-mps.
As we will see in the next section, EmulationConfig can also be used to configure the remote emulators.
Remote Emulator
Section titled “Remote Emulator”Remote emulators run on cloud infrastructure, potentially allowing to simulate larger quantum systems. As anticipated, for remote workflows credentials to create a connection are required. Here we will show how to create the specific handler of Pasqal Cloud services. Again, for more information about Pasqal Cloud and other providers, please refer to the Pasqal Cloud website (external).
Let's first initialize a connection. Without providing actual credentials, we can only create an empty object, for displaying purposes only:
from pulser_pasqal import PasqalCloud
# connection = PasqalCloud(# username=USERNAME, # Your username or email address for the Pasqal Cloud Platform# password=PASSWORD, # The password for your Pasqal Cloud Platform account# project_id=PROJECT_ID, # The ID of the project associated to your account# )connection = PasqalCloud()To use such connection to submit jobs, we first need to initialize a remote emulator:
from qoolqit.execution import RemoteEmulator
remote_emulator = RemoteEmulator(connection=connection)The RemoteEmulator supports the equivalent remote version of the above-mentioned backends, respectively EmuFreeBackendV2 (default), EmuSVBackend and EmuMPSBackend.
Depending on your provider, however, some emulator backend might not be available to use.
As before, also RemoteEmulator can be instantiated with:
backend_type: remote counterpart of local backends, namelyEmuFreeBackendV2(default),EmuSVBackend,EmuMPSBackend.emulation_config: same as local emulators. See Emulation configuration.num_shots: same as local emulators.
As an example, below, we specify to emulate the program with the EmuFreeBackendV2 and a custom EmulationConfig:
from qoolqit.execution import ( BackendType, EmulationConfig, Occupation, RemoteEmulator,)
# define the observables to be evaluated at the specified timesobservables = (Occupation(evaluation_times=[0.5, 1.0]),)# emulation configuration to add observables and include hardware modulationemulation_config = EmulationConfig(observables=observables, with_modulation=True)
# initialize the remote emulator with the custom configuration and num_shotsremote_emulator = RemoteEmulator( backend_type=BackendType.EmuFreeBackendV2, connection=connection, emulation_config=emulation_config, num_shots=1000,)Handling remote results: job and status management
Section titled “Handling remote results: job and status management”Remote emulators and QPU both have a run() method that will return a job handler.
job = remote_emulator.run(program)If your program requires intensive resources to be run, or if QPU happens to be on maintenance, results might not be immediately available. For this reason, the job handler provides several key methods for monitoring and retrieving results:
Querying Job Status:
# Check the current status of the jobstatus = job.get_status()print(f"Job status: {status}") # Returns: PENDING, RUNNING, DONE, ERROR, etc.Retrieving Job Results:
# Get results (blocks until completion for remote jobs)results = job.results()
# Get results with timeout (raises TimeoutError if exceeded)results = job.results(timeout=300) # 5 minutes timeoutPlease note that depending on the provider connection lifecycle, job.results() may not allow to wait indefinitely.
It is good practice to always check the status of the job before retrieving results.
Job Identification and Retrieval:
from qoolqit.execution import get_batch_id
# Get the unique batch identifier for the jobbatch_id = get_batch_id(job)
# Get the unique job identifierjob_id = job.job_id()
# For remote jobs, retrieve a job using its IDfrom qoolqit.execution import retrieve_remote_jobretrieved_job = retrieve_remote_job(connection, job_id, batch_id=batch_id)Note that local emulator jobs complete immediately and always return DONE status, while remote jobs (remote emulators and QPUs) may remain in PENDING or RUNNING states before completion.
Executing remotely on a QPU
Section titled “Executing remotely on a QPU”A connection object can also be used to run the program directly on a QPU.
Then, to see the list of available devices, run:
connection.fetch_available_devices(){'FRESNEL_CAN1': FRESNEL_CAN1, 'FRESNEL': FRESNEL}
A default QPU device is always available in the empty Pasqal Cloud connection, for demonstration purposes.
Now, to submit the program to the QPU, the program must be compiled for the specific QPU device before submission:
from qoolqit.devices import Device
device = Device.from_connection(connection, "FRESNEL")program.compile_to(device=device)Finally, to set up a QPU backend, there is no configuration and, as per the properties of the quantum hardware, results will come as a bitstrings counter of length specified by the num_shots parameter.
from qoolqit.execution import QPU
qpu = QPU(connection=connection, num_shots=500)Finally, submission and results handling work the same as for remote emulators.
Hardware Considerations
Section titled “Hardware Considerations”When running on QPUs, consider:
- Limited shots: QPU time is precious, so choose your number shots carefully
- Hardware constraints: Your program must be compiled for the specific QPU device
- Queue times: QPU jobs may wait in queue before execution
Summary
Section titled “Summary”This notebook covered the three main execution backends in QoolQit:
- Local Emulators: Fast, local simulation for development and testing
- Remote Emulators: Cloud-based simulation for larger quantum systems
- QPUs: Real quantum hardware execution
Each backend has its use cases, and the choice depends on your specific needs:
- Use local emulators for quick prototyping and small systems
- Use remote emulators for larger simulations beyond local capabilities
- Use QPUs for real quantum experiments and final validation
For more detailed configuration options for emulators, see the Extended Usage - Emulation Configuration section of this documentation.
