How to migrate to the new Boulder Opal package
Upgrade your pre-existing Boulder Opal code by the end of 2023
In this user guide we demonstrate the key points on how you can quickly and efficiently migrate your pre-existing Boulder Opal code from using the qctrl
package to the new Boulder Opal client package boulderopal
.
Migrating to the new Boulder Opal client package comes with many advantages meant to accelerate development pace and help you get the most out of your Boulder Opal subscription.
These benefits include:
- A simplified interface.
The new
boulderopal
client supports more natural object types (like NumPy arrays) and simpler syntax (for instance, requiring less code), especially for closed-loop optimization and noise reconstruction. - Improved error handling.
We are now able to detect errors on the client side that we could not previously with the legacy
qctrl
client, so the error and warnings are surfaced earlier to you, can be more informative, and ultimately save you compute time.
Installing the Boulder Opal Python client
You can install the Boulder Opal Python client using pip on the command line.
pip install boulder-opal
Overview of general interface changes
The motivation behind some of the changes outlined below was to make Boulder Opal accessible using a more Pythonic paradigm.
As you will see, the boulderopal
package provides a more intuitive and simple interface for accessing Boulder Opal's functionality. To achieve this, several substantial changes were made, as follows.
The Qctrl
object
The new boulderopal
package no longer features a Qctrl
object.
You can import it as a regular Python package and use its functions without reference to a session object.
import boulderopal as bo
Authentication
The first time you call a function from the boulderopal
package that involves the cloud, your Boulder Opal session will be authenticated.
This may mean that a browser window pops up asking you to log into the account associated with your Boulder Opal subscription, or if you've signed-in recently, it will automatically authenticate from a temporary token which gets saved on your local machine.
Organizations
If you're in more than one organization, you need to select which organization's resources to use when running calculations on the cloud.
This can be done with boulderopal.cloud.set_organization
, with your organization's slug.
To find your organization's slug, view your plan details in the Boulder Opal web app.
bo.cloud.set_organization("your-organization-slug")
Function input/output changes
You don't need to pass the parameters to functions and class constructors as keyword arguments (as you needed to in the legacy client).
The functions in the Boulder Opal client return a dictionary, instead of a custom Boulder Opal object.
This was done to make results more intuitive and easy to work with.
For example, to access the output of a graph calculation, use result["output"]
rather than the previous result.output
.
Graph calculations export PWC nodes as a dictionary with "durations"
, "values"
, and "time_dimension"
keys (rather than nested lists of "duration"
/"value"
dictionaries as in the legacy client).
The "durations"
and "values"
of the piecewise-constant function are represented as NumPy arrays, and the "time_dimension"
integer indicates the dimension of the values corresponding to time.
Functionality in the new client
Graph-based functionality
Instantiating a graph
You can instantiate a graph by calling its constructor at boulderopal.Graph
.
graph = bo.Graph()
Graph operations
Some graph operations have been moved.
- A new
graph.random
namespace contains graph operations with random behavior, such asgraph.random.uniform
. - A new
graph.ions
namespace contains graph operations dealing with trapped ion systems, such asgraph.ions.ms_phases
. - The legacy graph
utils
namespace has been removed; its operations can be directly accessed from the graph object viagraph.complex_optimizable_pwc_signal
,graph.real_optimizable_pwc_signal
, andgraph.filter_and_resample_pwc
.
Note that graph.filter_and_resample_pwc
now accepts a kernel
rather than a cutoff_frequency
, so you can now use this node with both a graph.gaussian_convolution_kernel
or a graph.sinc_convolution_kernel
.
Graph calculations
To execute graphs use boulderopal.execute_graph
.
Its inputs work very similarly to the legacy function qctrl.functions.calculate_graph
.
import numpy as np
graph = bo.Graph()
amplitude = np.pi * 1e5 # rad/s
duration = 5e-6 # s
pi_pulse = graph.constant_pwc(amplitude, duration)
infidelity = graph.infidelity_pwc(
hamiltonian=pi_pulse * graph.pauli_matrix("X"),
target=graph.target(graph.pauli_matrix("X")),
name="infidelity",
)
result = bo.execute_graph(graph, "infidelity")
print(f"π-pulse infidelity: {result['output']['infidelity']['value']:.3e}")
Your task (action_id="1812227") is queued.
Your task (action_id="1812227") has started.
Your task (action_id="1812227") has completed.
π-pulse infidelity: 4.441e-16
Graph optimization
To optimize graphs use boulderopal.run_optimization
.
Its inputs work very similarly to the legacy function qctrl.functions.calculate_optimization
.
import qctrlvisualizer as qv
# Define physical constraints.
omega_max = 2 * np.pi * 6e6 # Hz
delta_max = 2 * np.pi * 3e6 # Hz
segment_count = 32
duration = 200e-9 # s
graph = bo.Graph()
# Create optimizable signal.
envelope = graph.signals.cosine_pulse_pwc(
duration=duration, segment_count=segment_count, amplitude=1.0
)
omega = envelope * graph.complex_optimizable_pwc_signal(
segment_count=segment_count, maximum=omega_max, duration=duration
)
omega.name = r"$\Omega$"
# Create Hamiltonian and define infidelity.
hamiltonian = graph.hermitian_part(omega * graph.pauli_matrix("X"))
infidelity = graph.infidelity_pwc(
hamiltonian=hamiltonian,
target=graph.target(operator=graph.pauli_matrix("X")),
name="infidelity",
)
# Run the optimization.
result = bo.run_optimization(
graph=graph,
cost_node_name="infidelity",
output_node_names=r"$\Omega$",
optimization_count=4,
)
print(f"Optimized infidelity: {result['cost']:.3e}")
qv.plot_controls(result["output"], polar=False)
Your task (action_id="1812228") has started.
Your task (action_id="1812228") has completed.
Optimized infidelity: 1.110e-14
You can similarly use the other graph-based optimizers with boulderopal.run_stochastic_optimization
or boulderopal.run_gradient_free_optimization
.
Closed-loop optimization
The functionality to perform closed-loop optimization is in the boulderopal.closed_loop
module, featuring two main functions to perform closed-loop optimizations: boulderopal.closed_loop.optimize
and boulderopal.closed_loop.step
.
boulderopal.closed_loop.optimize
sets up a closed-loop optimization loop and runs it until a stopping criterion is met.
It works very similarly to the legacy client's toolkit function qctrl.closed_loop.optimize
, although there are a few small differences.
See the function's reference page or this user guide for more information.
In those cases where boulderopal.closed_loop.optimize
is not flexible enough, you can set up your own loop using boulderopal.closed_loop.step
, which performs a single closed-loop optimization step.
This works very similarly to the legacy function qctrl.functions.calculate_closed_loop_optimization_step
, although we have simplified its inputs to make them more intuitive.
See the function's reference page or this user guide for more information.
Noise reconstruction
The noise reconstruction functionality has been rebuilt from the ground up to have more natural inputs. Please look at the reference documentation and this user guide for more details.
Pulse library
The (non-graph-based) pulse library (originally in qctrl.signals
) can be accessed through the boulderopal.signals
namespace.
cosine_pulse = bo.signals.cosine_pulse(duration=10e-9, amplitude=5e6, drag=0.1)
cosine_pulse.export_with_time_step(1e-9)
array([ 122358.70926212-4.85402760e+13j, 1030536.86926882-1.27080092e+14j,
2500000. -1.57079633e+14j, 3969463.13073118-1.27080092e+14j,
4877641.29073788-4.85402760e+13j, 4877641.29073788+4.85402760e+13j,
3969463.13073118+1.27080092e+14j, 2500000. +1.57079633e+14j,
1030536.86926882+1.27080092e+14j, 122358.70926212+4.85402760e+13j])
Ions and superconducting toolkits
The system-specific toolkits (originally in qctrl.ions
and qctrl.superconducting
) can be found in the boulderopal.ions
and boulderopal.superconducting
modules.
The functionality remains the same, but some of the function signatures have changed slightly. For example, the parameter order has been tweaked, and the returned object is a direct graph calculation object (so the keys and contents of the resulting dictionary are different). See the reference documentation for the ions and superconducting modules for more information.
Managing calculations
Parallel calculations
Depending on your Boulder Opal plan and the number of available machines in your environment, you can run calculations in parallel.
You can preprovision your environment before submitting your tasks by bringing machines online using boulderopal.cloud.request_machines
.
You can also group multiple calculations and queue them together with boulderopal.cloud.group_requests
, like with the legacy function qctrl.parallel
.
The results of the grouped calculations will be returned at the same time.
For more information, you can read the Computational resources in Boulder Opal topic or consult the reference documentation.
Retrieving results
You can retrieve results from previous calculations using boulderopal.cloud.get_result
.
This is equivalent to the legacy qctrl.get_result
.
Removed functionality
Simulation functions
The legacy simulation functions (qctrl.functions.calculate_colored_noise_simulation
, qctrl.functions.calculate_coherent_simulation
, and qctrl.functions.calculate_quasi_static_scan
) have been replaced by graph calculations.
Please use a graph calculation instead.
For more information, some relevant user guides about representing quantum systems in graphs, performing noiseless simulations, performing quasi-static scans, and performing simulations involving noise.
Filter functions
The legacy function to calculate filter functions (qctrl.functions.calculate_filter_function
) has also been replaced by graph calculations in the new client, using the graph.filter_function
operation.
Calculating them in a graph is more flexible and faster.
You can find more information in this user guide.
Cross-entropy closed-loop optimizer
The cross-entropy method is not available in the new client. You can use any of the other closed-loop optimizers as they're bound to return better results.
Reinforcement learning
The reinforcement learning features from the Q-CTRL Python package have not been migrated to the new client yet.