qoolqit
qoolqit
Section titled “
qoolqit
”A Python library for algorithm development in the Rydberg Analog Model.
Modules:
-
devices– -
drive– -
embedding–Collection of graph and matrix embedding algorithms.
-
exceptions– -
execution–QoolQit module to execute quantum programs on QPUs or local/remote emulators.
-
graphs–Graph creation and manipulation in QoolQit.
-
program– -
register– -
waveforms–
Classes:
-
AnalogDevice–A realistic device for analog sequence execution.
-
Blackman–A Blackman window of a specified duration and area under the curve.
-
Constant–A constant waveform over a given duration.
-
DataGraph–The main graph structure to represent problem data.
-
Delay–An empty waveform.
-
Device–QoolQit Device wrapper around a Pulser BaseDevice.
-
DigitalAnalogDevice–A device with digital and analog capabilities.
-
Drive–The drive Hamiltonian acting over a duration.
-
Interpolated–A waveform created from interpolation of a set of data points.
-
MockDevice–A virtual device for unconstrained prototyping.
-
PiecewiseLinear–A piecewise linear waveform.
-
QuantumProgram–A program representing a Sequence acting on a Register of qubits.
-
Ramp–A ramp that linearly interpolates between an initial and final value.
-
Register–The Register in QoolQit, representing a set of qubits with coordinates.
-
SequenceCompiler–Compiles a QoolQit Register and Drive to a Device.
-
Sin–An arbitrary sine over a given duration.
Functions:
-
available_default_devices–Show the default available devices in QooQit.
AnalogDevice()
Section titled “
AnalogDevice()
”A realistic device for analog sequence execution.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Blackman(duration: float, area: float)
Section titled “
Blackman(duration: float, area: float)
”A Blackman window of a specified duration and area under the curve.
Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration
See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window (external)
Parameters:
duration
Section titled “ duration
”float)
–The waveform duration.
float)
–The integral of the waveform.
Example
blackman_wf = Blackman(100.0, area=3.14)Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None: """Initializes a new Blackman waveform.""" super().__init__(duration, area=area)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Constant(duration: float, value: float)
Section titled “
Constant(duration: float, value: float)
”A constant waveform over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
value
Section titled “ value
”float)
–the value to take during the duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, value: float,) -> None: super().__init__(duration, value=value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
DataGraph(edges: Iterable = [])
Section titled “
DataGraph(edges: Iterable = [])
”The main graph structure to represent problem data.
Parameters:
edges
Section titled “ edges
”Iterable, default:[])
–set of edge tuples (i, j)
- API reference
Methods:
-
circle–Constructs a circle graph, with the respective coordinates.
-
distances–Returns a dictionary of distances for a given set of edges.
-
draw–Draw the graph.
-
from_coordinates–Construct a base graph from a set of coordinates.
-
from_matrix–Constructs a graph from a symmetric square matrix.
-
from_nodes–Construct a base graph from a set of nodes.
-
from_nx–Convert a NetworkX Graph object into a QoolQit graph instance.
-
from_pyg–Convert a PyTorch Geometric Data object into a DataGraph instance.
-
heavy_hexagonal–Constructs a heavy-hexagonal lattice graph, with respective coordinates.
-
hexagonal–Constructs a hexagonal lattice graph, with respective coordinates.
-
interactions–Rydberg model interaction 1/r^6 between pair of nodes.
-
is_ud_graph–Check if the graph is unit-disk.
-
line–Constructs a line graph, with the respective coordinates.
-
max_distance–Returns the maximum distance in the graph.
-
min_distance–Returns the minimum distance in the graph.
-
random_er–Constructs an Erdős–Rényi random graph.
-
random_ud–Constructs a random unit-disk graph.
-
rescale_coords–Rescales the node coordinates by a factor.
-
set_ud_edges–Reset the set of edges to be equal to the set of unit-disk edges.
-
square–Constructs a square lattice graph, with respective coordinates.
-
to_pyg–Convert the DataGraph to a PyTorch Geometric Data object.
-
triangular–Constructs a triangular lattice graph, with respective coordinates.
-
ud_edges–Returns the set of edges given by the intersection of circles of a given radius.
-
ud_radius_range–Return the range (R_min, R_max) where the graph is unit-disk.
Attributes:
-
all_node_pairs(set) –Return a list of all possible node pairs in the graph.
-
coords(dict) –Return the dictionary of node coordinates.
-
edge_weights(dict) –Return the dictionary of edge weights.
-
has_coords(bool) –Check if the graph has coordinates.
-
has_edge_weights(bool) –Check if the graph has edge weights.
-
has_edges(bool) –Check if the graph has edges.
-
has_node_weights(bool) –Check if the graph has node weights.
-
node_weights(dict) –Return the dictionary of node weights.
-
sorted_edges(set) –Returns the set of edges (u, v) such that (u < v).
Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None: """ Default constructor for the BaseGraph.
Arguments: edges: set of edge tuples (i, j) """ super().__init__(edges)
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
edge_weights: dict
property
writable
Section titled “
edge_weights: dict
property
writable
”Return the dictionary of edge weights.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
node_weights: dict
property
writable
Section titled “
node_weights: dict
property
writable
”Return the dictionary of node weights.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”Returns the set of edges (u, v) such that (u < v).
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
Section titled “
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
”Constructs a circle graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
center
Section titled “ center
”tuple, default:(0.0, 0.0))
–point (x, y) to set as the center of the graph.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef circle( cls, n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0),) -> DataGraph: """Constructs a circle graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. center: point (x, y) to set as the center of the graph. """
d_theta = (2.0 * np.pi) / n r = spacing / (2.0 * np.sin(np.pi / n)) theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n) coords = [ (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta)) ] edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)] graph = cls.from_coordinates(coords) graph.add_edges_from(edges) graph._reset_dicts() return graph
distances(edge_list: Iterable | None = None) -> dict
Section titled “
distances(edge_list: Iterable | None = None) -> dict
”Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Parameters:
edge_list
Section titled “ edge_list
”Iterable | None, default:None)
–set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
(Axes | None, default:None)
–Axes object to draw on. If None, uses the current Axes.
**kwargs
Section titled “ **kwargs
”Any, default:{})
–keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
coords
Section titled “ coords
”list | dict)
–list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_matrix(data: ArrayLike) -> DataGraph
classmethod
Section titled “
from_matrix(data: ArrayLike) -> DataGraph
classmethod
”Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Parameters:
(ArrayLike)
–symmetric square matrix.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_matrix(cls, data: ArrayLike) -> DataGraph: """Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Arguments: data: symmetric square matrix. """ if data.ndim != 2: raise ValueError("2D Matrix required.") if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32): raise ValueError("Matrix must be symmetric.")
diag = np.diag(data) n_nodes = len(diag) node_weights = {i: diag[i] for i in range(n_nodes)} if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32): node_weights = {i: None for i in range(n_nodes)} else: node_weights = {i: diag[i].item() for i in range(n_nodes)}
data[data <= ATOL_32] = 0.0 non_zero = data.nonzero() i_list = non_zero[0].tolist() j_list = non_zero[1].tolist()
edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j] edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}
graph = cls.from_nodes(range(n_nodes)) graph.add_edges_from(edge_list) graph.node_weights = node_weights graph.edge_weights = edge_weights return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
nodes
Section titled “ nodes
”Iterable)
–set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
Section titled “
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
”Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires torch_geometric. Uses to_networkx internally.
Default attributes copied (if present on data ):
- Node:
x,pos(posis also stored in_coords) - Edge:
edge_attr - Graph:
y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit weights (_node_weights, _edge_weights) are not
populated automatically — use the explicit parameters:
node_weights_attr: real-valued tensor of shape(N,)or(N, 1). Defaults toNone.edge_weights_attr: real-valued tensor of shape(E,)or(E, 1)whereE = edge_index.shape[1](directed count). Defaults toNone.
The weight attribute is also stored as a regular node/edge attribute.
Parameters:
(Data)
–PyTorch Geometric Data object to convert.
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to copy (beyond x and pos).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to copy (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to copy (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str | None, default:None)
–Data attribute to use as node weights.
edge_weights_attr
Section titled “ edge_weights_attr
”str | None, default:None)
–Data attribute to use as edge weights.
Returns:
-
DataGraph–DataGraph with
_coords,_node_weights,_edge_weights -
DataGraph–populated where applicable.
Raises:
-
ImportError–if
torch_geometricis not installed. -
TypeError–if
datais not atorch_geometric.data.Datainstance, or if a weight attribute is not atorch.Tensor. -
AttributeError–if a specified weight attribute is missing.
-
ValueError–if a weight tensor has an incompatible shape or size.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_pyg( cls, data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None,) -> DataGraph: """Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires ``torch_geometric``. Uses ``to_networkx`` internally.
**Default attributes copied (if present on** ``data`` **):**
- Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``) - Edge: ``edge_attr`` - Graph: ``y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit weights** (``_node_weights``, ``_edge_weights``) are not populated automatically — use the explicit parameters:
- ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or ``(N, 1)``. Defaults to ``None``. - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count). Defaults to ``None``.
The weight attribute is also stored as a regular node/edge attribute.
Arguments: data: PyTorch Geometric Data object to convert. node_attrs: extra node attributes to copy (beyond x and pos). edge_attrs: extra edge attributes to copy (beyond edge_attr). graph_attrs: extra graph-level attributes to copy (beyond y). node_weights_attr: Data attribute to use as node weights. edge_weights_attr: Data attribute to use as edge weights.
Returns: DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights`` populated where applicable.
Raises: ImportError: if ``torch_geometric`` is not installed. TypeError: if ``data`` is not a ``torch_geometric.data.Data`` instance, or if a weight attribute is not a ``torch.Tensor``. AttributeError: if a specified weight attribute is missing. ValueError: if a weight tensor has an incompatible shape or size. """ try: from torch_geometric.data import Data from torch_geometric.utils import to_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
if not isinstance(data, Data): raise TypeError("Input must be a torch_geometric.data.Data object.")
# Validate weight attrs early and keep the squeezed tensors node_tensor = ( cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node") if node_weights_attr is not None else None ) edge_tensor = ( cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge") if edge_weights_attr is not None else None )
# Select unique attributes and add default ones only if present in the data node_attrs_set = {k for k in {"x", "pos"} if k in data} if node_attrs is not None: node_attrs_set |= set(node_attrs) if node_weights_attr is not None: node_attrs_set.add(node_weights_attr)
edge_attrs_set = {k for k in {"edge_attr"} if k in data} if edge_attrs is not None: edge_attrs_set |= set(edge_attrs) if edge_weights_attr is not None: edge_attrs_set.add(edge_weights_attr)
graph_attrs_set = {k for k in {"y"} if k in data} if graph_attrs is not None: graph_attrs_set |= set(graph_attrs)
# Convert to NetworkX (undirected, no self-loops) nx_graph = to_networkx( data, node_attrs=list(node_attrs_set), edge_attrs=list(edge_attrs_set), graph_attrs=list(graph_attrs_set), to_undirected=True, remove_self_loops=True, )
# Build the DataGraph: edges carry their data, nodes carry their data graph = cls(nx_graph.edges(data=True)) graph.add_nodes_from(nx_graph.nodes(data=True)) graph.graph = nx_graph.graph
# Re-initialize QoolQit internal dicts for all nodes/edges graph._coords = {n: None for n in graph.nodes} graph._reset_dicts()
# pos → _coords (stored as list [x, y] by to_networkx) for node, node_data in nx_graph.nodes(data=True): if "pos" in node_data: graph._coords[node] = tuple(node_data["pos"]) # type: ignore[assignment]
# node_weights_attr → _node_weights if node_tensor is not None: for i in range(data.num_nodes): graph._node_weights[i] = node_tensor[i].item()
# edge_weights_attr → _edge_weights if edge_tensor is not None: seen: set = set() for idx in range(data.edge_index.shape[1]): u = int(data.edge_index[0, idx].item()) v = int(data.edge_index[1, idx].item()) key = (min(u, v), max(u, v)) if key not in seen: graph._edge_weights[key] = edge_tensor[idx].item() seen.add(key)
return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Notes
Source code in qoolqit/graphs/data_graph.py
@classmethoddef heavy_hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice.
Notes: The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site. """ G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True) pos_unit = nx.get_node_attributes(G_hex, "pos")
G_heavy = nx.Graph() scaling_factor = 2 * spacing
label_map = {} for old_label, (x, y) in pos_unit.items(): # Relabel to an even-integer grid to make space for midpoint nodes new_label = (2 * old_label[0], 2 * old_label[1]) label_map[old_label] = new_label
# Scale positions and add the node to the new graph new_pos = (x * scaling_factor, y * scaling_factor) G_heavy.add_node(new_label, pos=new_pos)
for u_old, v_old in G_hex.edges(): u_new, v_new = label_map[u_old], label_map[v_old]
mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)
pos_u = G_heavy.nodes[u_new]["pos"] pos_v = G_heavy.nodes[v_new]["pos"] mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)
G_heavy.add_node(mid_label, pos=mid_pos) G_heavy.add_edge(u_new, mid_label) G_heavy.add_edge(mid_label, v_new)
final_nodes = sorted(list(G_heavy.nodes()))
final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]
label_to_int = {label: i for i, label in enumerate(final_nodes)}
final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]
graph = cls.from_coordinates(final_coords) graph.add_edges_from(final_edges) graph._reset_dicts() return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.hexagonal_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a line graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef line(cls, n: int, spacing: float = 1.0) -> DataGraph: """Constructs a line graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. """ coords = [(i * spacing, 0.0) for i in range(n)] graph = cls.from_coordinates(coords) edges = [(i, i + 1) for i in range(0, n - 1)] graph.add_edges_from(edges) graph._reset_dicts() return graph
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = min(self.distances(self.all_node_pairs).values()) elif connected: distance = min(self.distances(self.sorted_edges).values()) else: distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
Section titled “
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
”Constructs an Erdős–Rényi random graph.
Parameters:
(int)
–number of nodes.
float)
–probability that any two nodes connect.
int | None, default:None)
–random seed.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph: """Constructs an Erdős–Rényi random graph.
Arguments: n: number of nodes. p: probability that any two nodes connect. seed: random seed. """ base_graph = nx.erdos_renyi_graph(n, p, seed) graph = DataGraph.from_nodes(list(base_graph.nodes)) graph.add_edges_from(base_graph.edges) graph._reset_dicts() return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
Section titled “
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
”Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Parameters:
(int)
–number of nodes.
radius
Section titled “ radius
”float, default:1.0)
–radius to use for defining the unit-disk edges.
float | None, default:None)
–size of the square on which to sample the node coordinates.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_ud( cls, n: int, radius: float = 1.0, L: float | None = None,) -> DataGraph: """Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Arguments: n: number of nodes. radius: radius to use for defining the unit-disk edges. L: size of the square on which to sample the node coordinates. """ if L is None: L = (radius / 2) * ((np.pi * n) ** 0.5) coords = random_coords(n, L) graph = cls.from_coordinates(coords) edges = graph.ud_edges(radius) graph.add_edges_from(edges) graph._reset_dicts() return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
Section titled “
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
scaling
Section titled “ scaling
”float | None, default:None)
–value to scale by.
spacing
Section titled “ spacing
”float | None, default:None)
–value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None: """Reset the set of edges to be equal to the set of unit-disk edges.""" super().set_ud_edges(radius=radius) self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a square lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of square.
int)
–Number of columns of square.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef square( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a square lattice graph, with respective coordinates.
Arguments: m: Number of rows of square. n: Number of columns of square. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.grid_2d_graph(m, n) final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)] G = nx.convert_node_labels_to_integers(G)
graph = DataGraph.from_coordinates(final_coords) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
Section titled “
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
”Convert the DataGraph to a PyTorch Geometric Data object.
Requires torch_geometric. Uses from_networkx internally.
Default attributes exported (if present on the graph):
- Node
"x"→data.x; Edge"edge_attr"→data.edge_attr - Graph
"y"→data.y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit internal dicts exported when populated:
_coords→data.pos(float64, shape(N, 2))_node_weights→data.<node_weights_attr>(float64, shape(N,)). Defaults to"weight"._edge_weights→data.<edge_weights_attr>(float64, shape(2*E,)). Defaults to"edge_weight".
Parameters:
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to export (beyond x).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to export (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to export (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str, default:'weight')
–Data attribute name for node weights.
Defaults to "weight".
edge_weights_attr
Section titled “ edge_weights_attr
”str, default:'edge_weight')
–Data attribute name for edge weights.
Defaults to "edge_weight".
Returns:
-
Data–PyTorch Geometric Data object.
Raises:
-
ImportError–if
torch_geometricis not installed.
Source code in qoolqit/graphs/data_graph.py
def to_pyg( self, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = "weight", edge_weights_attr: str = "edge_weight",) -> torch_geometric.data.Data: """Convert the DataGraph to a PyTorch Geometric Data object.
Requires ``torch_geometric``. Uses ``from_networkx`` internally.
**Default attributes exported (if present on the graph):**
- Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr`` - Graph ``"y"`` → ``data.y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit internal dicts exported when populated:**
- ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``) - ``_node_weights`` → ``data.`` (float64, shape ``(N,)``). Defaults to ``"weight"``. - ``_edge_weights`` → ``data.`` (float64, shape ``(2*E,)``). Defaults to ``"edge_weight"``.
Arguments: node_attrs: extra node attributes to export (beyond x). edge_attrs: extra edge attributes to export (beyond edge_attr). graph_attrs: extra graph-level attributes to export (beyond y). node_weights_attr: Data attribute name for node weights. Defaults to ``"weight"``. edge_weights_attr: Data attribute name for edge weights. Defaults to ``"edge_weight"``.
Returns: PyTorch Geometric Data object.
Raises: ImportError: if ``torch_geometric`` is not installed. """ try: import torch from torch_geometric.utils import from_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
node_attrs_set = set(node_attrs) if node_attrs else set() edge_attrs_set = set(edge_attrs) if edge_attrs else set() graph_attrs_list = list(graph_attrs) if graph_attrs else []
# Add default PyG attributes if present in the graph if any("x" in d for _, d in self.nodes(data=True)): node_attrs_set.add("x") if any("edge_attr" in d for _, _, d in self.edges(data=True)): edge_attrs_set.add("edge_attr") if "y" in self.graph: graph_attrs_list.append("y")
# Build a filtered copy with only the requested attributes filtered_graph = nx.Graph() filtered_graph.add_nodes_from(self.nodes()) filtered_graph.add_edges_from(self.edges())
for node, node_data in self.nodes(data=True): for key, value in node_data.items(): if key in node_attrs_set: filtered_graph.nodes[node][key] = value for u, v, edge_data in self.edges(data=True): for key, value in edge_data.items(): if key in edge_attrs_set: filtered_graph.edges[u, v][key] = value for attr in graph_attrs_list: if attr in self.graph: filtered_graph.graph[attr] = self.graph[attr]
data = from_networkx(filtered_graph)
# Export _coords → pos if self.has_coords: positions = [self._coords[n] for n in sorted(self.nodes())] data.pos = torch.tensor(positions, dtype=torch.float64)
# Export _node_weights → node_weights_attr if self.has_node_weights: weights = [self._node_weights[n] for n in sorted(self.nodes())] setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))
# Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index) if self.has_edge_weights: edge_weights: list[float] = [] for i in range(data.edge_index.shape[1]): u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item()) edge_key = (min(u, v), max(u, v)) edge_weights.append(float(self._edge_weights[edge_key])) # type: ignore[arg-type] setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))
return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a triangular lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of triangles.
int)
–Number of columns of triangles.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef triangular( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a triangular lattice graph, with respective coordinates.
Arguments: m: Number of rows of triangles. n: Number of columns of triangles. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.triangular_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
radius
Section titled “ radius
”float)
–the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
”An empty waveform.
Parameters:
duration
Section titled “ duration
”float)
–the total duration of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def __init__( self, duration: float, *args: float, **kwargs: float | np.ndarray,) -> None: """Initializes the Waveform.
Arguments: duration: the total duration of the waveform. """
if duration <= 0: raise ValueError("Duration needs to be a positive non-zero value.")
if len(args) > 0: raise ValueError( f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments" )
self._duration = duration self._params_dict = kwargs
self._max: float | None = None self._min: float | None = None
for key, value in kwargs.items(): setattr(self, key, value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
Section titled “
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
”QoolQit Device wrapper around a Pulser BaseDevice.
Parameters:
pulser_device
Section titled “ pulser_device
”BaseDevice)
–a BaseDevice to build the QoolQit device from.
default_converter
Section titled “ default_converter
”Optional[UnitConverter], default:None)
–optional unit converter to handle unit conversion.
Examples:
From Pulser device:
qoolqit_device = Device(pulser_device=pulser_device)From remote Pulser device:
from pulser_pasqal import PasqalCloudfrom qoolqit import Device
# Fetch the remote device from the connectionconnection = PasqalCloud()pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Devicefresnel_device = Device(pulser_device=PulserFresnelDevice)From custom Pulser device:
from dataclasses import replacefrom pulser import AnalogDevicefrom qoolqit import Device
# Converting the pulser Device object in a VirtualDevice objectVirtualAnalog = AnalogDevice.to_virtual()# Replacing desired valuesModdedAnalogDevice = replace( VirtualAnalog, max_radial_distance=100, max_sequence_duration=7000 )
# Wrap a Pulser device object into a QoolQit Devicemod_analog_device = Device(pulser_device=ModdedAnalogDevice)- API reference
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__( self, pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None,) -> None:
if not isinstance(pulser_device, BaseDevice): raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")
# Store it for all subsequent lookups self._pulser_device: BaseDevice = pulser_device self._name: str = self._pulser_device.name
# Physical constants / channel & limit lookups (assumes 'rydberg_global' channel) self._C6 = self._pulser_device.interaction_coeff self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period # Relevant limits from the underlying device (float or None) self._max_duration = self._pulser_device.max_sequence_duration self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp self._upper_amp = self._max_amp or 4 * math.pi self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning self._min_distance = self._pulser_device.min_atom_distance self._lower_distance = self._min_distance or 5.0 self._max_radial_distance = self._pulser_device.max_radial_distance
# ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6 self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6
# layouts self._requires_layout = self._pulser_device.requires_layout
if default_converter is not None: # Snapshot the caller-provided factors so reset() reproduces them exactly. t0, e0, d0 = default_converter.factors self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter( self._C6, t0, e0, d0 ) else: self._default_factory = lambda: UnitConverter.from_distance( self._C6, self._lower_distance )
self.reset_converter()
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
DigitalAnalogDevice()
Section titled “
DigitalAnalogDevice()
”A device with digital and analog capabilities.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Drive(*, amplitude: Waveform, detuning: Waveform | None = None, dmm: DetuningMapModulator | None = None, phase: float = 0.0)
Section titled “
Drive(*, amplitude: Waveform, detuning: Waveform | None = None, dmm: DetuningMapModulator | None = None, phase: float = 0.0)
”The drive Hamiltonian acting over a duration.
The Drive specifies the control parameters for the time-dependent drive Hamiltonian in the Rydberg model (see https://docs.pasqal.com/qoolqit/get_started/qoolqit_model/ (external) for details),
H_drive(t) = Σᵢ [Ω(t)/2 (cos φ(t) σˣᵢ - sin φ(t) σʸᵢ)] - Σᵢ [δ(t) + εᵢ Δ(t)] nᵢ
representing: - Amplitude Ω(t): Controls the Rabi frequency that drives qubits. - Detuning δ(t): Controls the energy offset of the Rydberg state. - dmm εᵢ, Δ(t): Detuning Map Modulator (DMM) for additional qubit-specific detunings. - Phase φ: Global phase applied to the amplitude term.
Parameters:
amplitude
Section titled “ amplitude
”Waveform)
–Time-dependent amplitude waveform Ω(t) representing the Rabi frequency. Controls the strength of the coupling between ground and Rydberg states. Must be positive for all times.
detuning
Section titled “ detuning
”Waveform | None, default:None)
–Time-dependent detuning waveform δ(t) representing the energy offset of the Rydberg state relative to resonance. If None, defaults to zero detuning (Delay waveform) for the duration of the amplitude.
DetuningMapModulator | None, default:None)
–DetuningMapModulator instance for additional negative detuning waveform Δ(t) ≤ 0
applied to individual qubits as specified by its weights attribute εᵢ.
phase
Section titled “ phase
”float, default:0.0)
–Global phase φ applied to the amplitude term in the Hamiltonian. Defaults to 0.0 (no phase).
Raises:
-
TypeError–If amplitude or detuning are not Waveform instances.
-
ValueError–If the amplitude waveform has negative values.
Note
Example
Attributes:
-
amplitude(Waveform) –The amplitude waveform in the drive.
-
detuning(Waveform) –The detuning waveform in the drive.
-
dmm(DetuningMapModulator | None) –Detuning Map Modulator (DMM) applied to individual qubits.
-
phase(float) –The phase value in the drive.
Source code in qoolqit/drive.py
def __init__( self, *, amplitude: Waveform, detuning: Waveform | None = None, dmm: DetuningMapModulator | None = None, phase: float = 0.0,) -> None: """Initialize a Drive.
The Drive specifies the control parameters for the time-dependent drive Hamiltonian in the Rydberg model (see https://docs.pasqal.com/qoolqit/get_started/qoolqit_model/ for details),
H_drive(t) = Σᵢ [Ω(t)/2 (cos φ(t) σˣᵢ - sin φ(t) σʸᵢ)] - Σᵢ [δ(t) + εᵢ Δ(t)] nᵢ
representing: - Amplitude Ω(t): Controls the Rabi frequency that drives qubits. - Detuning δ(t): Controls the energy offset of the Rydberg state. - dmm εᵢ, Δ(t): Detuning Map Modulator (DMM) for additional qubit-specific detunings. - Phase φ: Global phase applied to the amplitude term.
Args: amplitude: Time-dependent amplitude waveform Ω(t) representing the Rabi frequency. Controls the strength of the coupling between ground and Rydberg states. Must be positive for all times. detuning: Time-dependent detuning waveform δ(t) representing the energy offset of the Rydberg state relative to resonance. If None, defaults to zero detuning (Delay waveform) for the duration of the amplitude. dmm: DetuningMapModulator instance for additional negative detuning waveform Δ(t) ≤ 0 applied to individual qubits as specified by its `weights` attribute εᵢ. phase: Global phase φ applied to the amplitude term in the Hamiltonian. Defaults to 0.0 (no phase).
Raises: TypeError: If amplitude or detuning are not Waveform instances. ValueError: If the amplitude waveform has negative values.
Note: - All arguments must be passed as keyword arguments. - If amplitude and detuning have different durations, the shorter one is automatically extended with a Delay to match the longer duration. - DetuningMapModulator waveform must be negative for all times (≤ 0) as it represents energy shifts below the resonance.
Example: >>> from qoolqit import Drive >>> from qoolqit.waveforms import Constant, Ramp >>> >>> # Simple constant drive >>> drive = Drive(amplitude=Constant(10.0, 1.5)) >>> >>> # Drive with time-varying amplitude and detuning >>> amp = Ramp(5.0, 0.0, 2.0) >>> det = Constant(5.0, -1.0) >>> drive = Drive(amplitude=amp, detuning=det, phase=0.5) """
for arg in [amplitude, detuning]: if arg is not None and not isinstance(arg, Waveform): raise TypeError("'amplitude' and 'detuning' must be of type Waveform.")
if amplitude.min() < 0.0: raise ValueError("'amplitude' must be positive.")
self._amplitude = amplitude self._detuning = detuning if detuning is not None else Delay(amplitude.duration)
self._amplitude_orig = self._amplitude self._detuning_orig = self._detuning
# adjust amplitude and detuning waveforms to match the duration if self._amplitude.duration > self._detuning.duration: extra_duration = self._amplitude.duration - self._detuning.duration self._detuning = CompositeWaveform(self._detuning, Delay(extra_duration)) elif self._detuning.duration > self._amplitude.duration: extra_duration = self._detuning.duration - self._amplitude.duration self._amplitude = CompositeWaveform(self._amplitude, Delay(extra_duration))
self._duration = self._amplitude.duration if dmm is not None and not isinstance(dmm, DetuningMapModulator): raise TypeError("'dmm' must be of type DetuningMapModulator.") self._dmm = dmm self._phase = phase
amplitude: Waveform
property
Section titled “
amplitude: Waveform
property
”The amplitude waveform in the drive.
detuning: Waveform
property
Section titled “
detuning: Waveform
property
”The detuning waveform in the drive.
dmm: DetuningMapModulator | None
property
Section titled “
dmm: DetuningMapModulator | None
property
”Detuning Map Modulator (DMM) applied to individual qubits.
phase: float
property
Section titled “
phase: float
property
”The phase value in the drive.
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
Section titled “
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
”A waveform created from interpolation of a set of data points.
Parameters:
duration
Section titled “ duration
”int)
–The waveform duration (in ns).
values
Section titled “ values
”ArrayLike)
–Values of the interpolation points. Must be a list of castable to float or a parametrized object.
times
Section titled “ times
”ArrayLike, default:None)
–Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.
interpolator
Section titled “ interpolator
”str, default:'PchipInterpolator')
–The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = "PchipInterpolator", **interpolator_kwargs: Any,): """Initializes a new Interpolated waveform.""" super().__init__(duration) self._values = np.array(values, dtype=float) if times: # fractional times in [0,1] if any([(ft < 0) or (ft > 1) for ft in times]): raise ValueError("All values in `times` must be in [0,1].") self._times = np.array(times, dtype=float) if len(times) != len(self._values): raise ValueError( "Arguments `values` and `times` must be arrays of the same length." ) else: self._times = np.linspace(0, 1, num=len(self._values))
if interpolator not in self._valid_interpolators: raise ValueError( f"Invalid interpolator '{interpolator}', only " "accepts: " + ", ".join(self._valid_interpolators) ) self._interpolator = interpolator self._interpolator_kwargs = interpolator_kwargs
interp_cls = getattr(interpolate, interpolator) self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
MockDevice()
Section titled “
MockDevice()
”A virtual device for unconstrained prototyping.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
PiecewiseLinear(durations: list | tuple, values: list | tuple)
Section titled “
PiecewiseLinear(durations: list | tuple, values: list | tuple)
”A piecewise linear waveform.
Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.
Parameters:
durations
Section titled “ durations
”list | tuple)
–list or tuple of N duration values.
values
Section titled “ values
”list | tuple)
–list or tuple of N+1 waveform values.
Methods:
-
function–Identifies the right waveform in the composition and evaluates it at time t.
-
max–Get the maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
durations(list[float]) –Returns the list of durations of each individual waveform.
-
n_waveforms(int) –Returns the number of waveforms.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
-
times(list[float]) –Returns the list of times when each individual waveform starts.
-
waveforms(list[Waveform]) –Returns a list of the individual waveforms.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, durations: list | tuple, values: list | tuple,) -> None: if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))): raise TypeError( "A PiecewiseLinear waveform requires a list or tuple of durations and values." )
if len(durations) + 1 != len(values) or len(durations) == 1: raise ValueError( "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2." )
for duration in durations: if duration == 0.0: raise ValueError("A PiecewiseLinear interval cannot have zero duration.")
self.values = values
wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]
super().__init__(*wfs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
durations: list[float]
property
Section titled “
durations: list[float]
property
”Returns the list of durations of each individual waveform.
n_waveforms: int
property
Section titled “
n_waveforms: int
property
”Returns the number of waveforms.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
times: list[float]
property
Section titled “
times: list[float]
property
”Returns the list of times when each individual waveform starts.
waveforms: list[Waveform]
property
Section titled “
waveforms: list[Waveform]
property
”Returns a list of the individual waveforms.
function(t: float) -> float
Section titled “
function(t: float) -> float
”Identifies the right waveform in the composition and evaluates it at time t.
Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float: """Identifies the right waveform in the composition and evaluates it at time t.""" idx = np.searchsorted(self.times, t, side="right") - 1 if idx == -1: return 0.0 if idx == self.n_waveforms: if t == self.times[-1]: idx = idx - 1 else: return 0.0
local_t = t - self.times[idx] value: float = self.waveforms[idx](local_t) return value
max() -> float
Section titled “
max() -> float
”Get the maximum value of the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the maximum value of the waveform.""" return max([wf.max() for wf in self.waveforms])
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
QuantumProgram(register: Register, drive: Drive)
Section titled “
QuantumProgram(register: Register, drive: Drive)
”A program representing a Sequence acting on a Register of qubits.
Parameters:
register
Section titled “ register
”Register)
–the register of qubits, defining their positions.
drive
Section titled “ drive
”Drive)
–the drive acting on qubits, defining amplitude, detuning and phase.
-
API reference
qoolqit.execution
execution
Methods:
-
compile_to–Compiles the quantum program for execution on a specific device.
Attributes:
-
compiled_sequence(Sequence) –The Pulser sequence compiled to a specific device.
-
drive(Drive) –The driving waveforms.
-
is_compiled(bool) –Check if the program has been compiled.
-
register(Register) –The register of qubits.
Source code in qoolqit/program.py
def __init__( self, register: Register, drive: Drive,) -> None:
if not isinstance(register, Register): raise TypeError("`register` must be of type Register.") self._register = register
if not isinstance(drive, Drive): raise TypeError("`drive` must be of type Drive.") if drive.dmm is not None: dmm_weights = drive.dmm.weights for qid in dmm_weights.keys(): if qid not in register.qubits: raise ValueError( "In this QuantumProgram, the drive's detuning modulator map (DMM) " f"and the register do not match: qubit {qid} appears in the DMM " "but is not defined in the register." )
self._drive = drive self._compiled_sequence: PulserSequence | None = None
compiled_sequence: PulserSequence
property
Section titled “
compiled_sequence: PulserSequence
property
”The Pulser sequence compiled to a specific device.
drive: Drive
property
Section titled “
drive: Drive
property
”The driving waveforms.
is_compiled: bool
property
Section titled “
is_compiled: bool
property
”Check if the program has been compiled.
register: Register
property
Section titled “
register: Register
property
”The register of qubits.
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
Section titled “
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
”Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints.
- CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Parameters:
device
Section titled “ device
”Device)
–The target device for compilation.
profile
Section titled “ profile
”CompilerProfile, default:MAX_ENERGY)
–The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises:
-
CompilationError–If the compilation fails due to device constraints.
Source code in qoolqit/program.py
def compile_to( self, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None,) -> None: """Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints. - CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Args: device: The target device for compilation. profile: The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY. device_max_duration_ratio: Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises: CompilationError: If the compilation fails due to device constraints. """
if device_max_duration_ratio is not None: if device._max_duration is None: raise ValueError( "Cannot set `device_max_duration_ratio` because the target device " "does not have a maximum allowed duration." ) if not (0 < device_max_duration_ratio <= 1): raise ValueError( "`device_max_duration_ratio` must be between 0 and 1, " f"got {device_max_duration_ratio} instead." )
# Check if device supports DMM and has a DMM channel if self.drive.dmm is not None: if not device._device.dmm_channels: raise CompilationError( "The device does not support DMM. Please use a device that supports DMM." )
compiler = SequenceCompiler( self.register, self.drive, device, profile, device_max_duration_ratio ) self._device = device self._compiled_sequence = compiler.compile_sequence()
Ramp(duration: float, initial_value: float, final_value: float)
Section titled “
Ramp(duration: float, initial_value: float, final_value: float)
”A ramp that linearly interpolates between an initial and final value.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
initial_value
Section titled “ initial_value
”float)
–the initial value at t = 0.
final_value
Section titled “ final_value
”float)
–the final value at t = duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, initial_value: float, final_value: float,) -> None: super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Register(qubits: dict)
Section titled “
Register(qubits: dict)
”The Register in QoolQit, representing a set of qubits with coordinates.
Parameters:
qubits
Section titled “ qubits
”dict)
–a dictionary of qubits and respective coordinates {q: (x, y), ...}.
Methods:
-
distances–Distance between each qubit pair.
-
draw–Draw the register.
-
from_coordinates–Initializes a Register from a list of coordinates.
-
from_graph–Initializes a Register from a graph that has coordinates.
-
interactions–Interaction 1/r^6 between each qubit pair.
-
max_radial_distance–Maximum radial distance between all qubits.
-
min_distance–Minimum distance between all qubit pairs.
-
radial_distances–Radial distance of each qubit from the origin.
Attributes:
-
n_qubits(int) –Number of qubits in the Register.
-
qubits(dict) –Returns the dictionary of qubits and respective coordinates.
-
qubits_ids(list) –Returns the qubit keys.
Source code in qoolqit/register.py
def __init__(self, qubits: dict) -> None: """Default constructor for the Register.
Arguments: qubits: a dictionary of qubits and respective coordinates {q: (x, y), ...}. """ if not isinstance(qubits, dict): raise TypeError( "Register must be initialized with a dictionary of " "qubits and respective coordinates {q: (x, y), ...}." )
self._qubits: dict = qubits
n_qubits: int
property
Section titled “
n_qubits: int
property
”Number of qubits in the Register.
qubits: dict
property
Section titled “
qubits: dict
property
”Returns the dictionary of qubits and respective coordinates.
qubits_ids: list
property
Section titled “
qubits_ids: list
property
”Returns the qubit keys.
distances() -> dict
Section titled “
distances() -> dict
”Distance between each qubit pair.
Source code in qoolqit/register.py
def distances(self) -> dict: """Distance between each qubit pair.""" pairs = all_node_pairs(list(self.qubits.keys())) return distances(self.qubits, pairs)
draw(return_fig: bool = False) -> Figure | None
Section titled “
draw(return_fig: bool = False) -> Figure | None
”Draw the register.
Parameters:
return_fig
Section titled “ return_fig
”bool, default:False)
–boolean argument to return the matplotlib figure.
Source code in qoolqit/register.py
def draw(self, return_fig: bool = False) -> Figure | None: """Draw the register.
Arguments: return_fig: boolean argument to return the matplotlib figure. """ fig, ax = plt.subplots(1, 1, figsize=(4, 4), dpi=150) ax.set_aspect("equal")
x_coords, y_coords = zip(*self.qubits.values()) x_min, x_max = min(x_coords), max(x_coords) y_min, y_max = min(y_coords), max(y_coords)
grid_x_min, grid_x_max = min(-1, x_min), max(1, x_max) grid_y_min, grid_y_max = min(-1, y_min), max(1, y_max)
grid_scale = ceil(max(grid_x_max - grid_x_min, grid_y_max - grid_y_min))
ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7) ax.set_axisbelow(True) ax.set_xlabel("x") ax.set_ylabel("y")
eps = 0.05 * grid_scale ax.set_xlim(grid_x_min - eps, grid_x_max + eps) ax.set_ylim(grid_y_min - eps, grid_y_max + eps)
possible_multiples = [0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0] grid_multiple = min(possible_multiples, key=lambda x: abs(x - grid_scale / 8)) majorLocatorX = MultipleLocator(grid_multiple) majorLocatorY = MultipleLocator(grid_multiple) ax.xaxis.set_major_locator(majorLocatorX) ax.yaxis.set_major_locator(majorLocatorY)
ax.scatter(x_coords, y_coords, s=50, color="darkgreen")
ax.tick_params(axis="both", which="both", labelbottom=True, labelleft=True, labelsize=8)
if return_fig: plt.close() return fig else: return None
from_coordinates(coords: list) -> Register
classmethod
Section titled “
from_coordinates(coords: list) -> Register
classmethod
”Initializes a Register from a list of coordinates.
Parameters:
coords
Section titled “ coords
”list)
–a list of coordinates [(x, y), ...]
Source code in qoolqit/register.py
@classmethoddef from_coordinates(cls, coords: list) -> Register: """Initializes a Register from a list of coordinates.
Arguments: coords: a list of coordinates [(x, y), ...] """ if not isinstance(coords, list): raise TypeError( "Register must be initialized with a dictionary of qubit and coordinates." ) coords_dict = {i: pos for i, pos in enumerate(coords)} return cls(coords_dict)
from_graph(graph: DataGraph) -> Register
classmethod
Section titled “
from_graph(graph: DataGraph) -> Register
classmethod
”Initializes a Register from a graph that has coordinates.
Parameters:
graph
Section titled “ graph
”DataGraph)
–a DataGraph instance.
Source code in qoolqit/register.py
@classmethoddef from_graph(cls, graph: DataGraph) -> Register: """Initializes a Register from a graph that has coordinates.
Arguments: graph: a DataGraph instance. """
if not graph.has_coords: raise ValueError("Initializing a register from a graph requires node coordinates.")
if len(graph.nodes) == 0: raise ValueError("Trying to initialize a register from an empty graph.")
return cls(graph.coords)
interactions() -> dict
Section titled “
interactions() -> dict
”Interaction 1/r^6 between each qubit pair.
Source code in qoolqit/register.py
def interactions(self) -> dict: """Interaction 1/r^6 between each qubit pair.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
max_radial_distance() -> float
Section titled “
max_radial_distance() -> float
”Maximum radial distance between all qubits.
Source code in qoolqit/register.py
def max_radial_distance(self) -> float: """Maximum radial distance between all qubits.""" max_radial_distance: float = max(self.radial_distances().values()) return max_radial_distance
min_distance() -> float
Section titled “
min_distance() -> float
”Minimum distance between all qubit pairs.
Source code in qoolqit/register.py
def min_distance(self) -> float: """Minimum distance between all qubit pairs.""" distance: float = min(self.distances().values()) return distance
radial_distances() -> dict
Section titled “
radial_distances() -> dict
”Radial distance of each qubit from the origin.
Source code in qoolqit/register.py
def radial_distances(self) -> dict: """Radial distance of each qubit from the origin.""" return radial_distances(self.qubits)
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
Section titled “
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
”Compiles a QoolQit Register and Drive to a Device.
Parameters:
register
Section titled “ register
”Register)
–the QoolQit Register.
drive
Section titled “ drive
”Drive)
–the QoolQit Drive.
device
Section titled “ device
”Device)
–the QoolQit Device.
profile
Section titled “ profile
”CompilerProfile)
–the CompilerProfile to use.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–optionally set the program duration to a fraction of the device's maximum allowed duration.
Source code in qoolqit/execution/sequence_compiler.py
def __init__( self, register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None,) -> None: """Initializes the compiler.
Args: register: the QoolQit Register. drive: the QoolQit Drive. device: the QoolQit Device. profile: the CompilerProfile to use. device_max_duration_ratio: optionally set the program duration to a fraction of the device's maximum allowed duration. """
self._register = register self._drive = drive self._device = device self._target_device = device._device self._profile = profile self._device_max_duration_ratio = device_max_duration_ratio self._compilation_function: Callable = basic_compilation
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
Section titled “
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
”An arbitrary sine over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
amplitude
Section titled “ amplitude
”float, default:1.0)
–the amplitude of the sine wave.
omega
Section titled “ omega
”float, default:1.0)
–the frequency of the sine wave.
float, default:0.0)
–the phase of the sine wave.
shift
Section titled “ shift
”float, default:0.0)
–the vertical shift of the sine wave.
Methods:
-
max–Get the approximate maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0,) -> None: super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
max() -> float
Section titled “
max() -> float
”Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._max is None: self._approximate_min_max() return cast(float, self._max)
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
available_default_devices() -> None
Section titled “
available_default_devices() -> None
”Show the default available devices in QooQit.
Source code in qoolqit/devices/device.py
def available_default_devices() -> None: """Show the default available devices in QooQit.""" for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()): dev.info()