Skip to content

Results are limited to the current section : Qoolqit

API reference

A Python library for algorithm development in the Rydberg Analog Model.

Modules:

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:

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)

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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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:

(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)

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.

A constant waveform over a given duration.

Parameters:

(float) –

the total duration.

(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)

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.

The main graph structure to represent problem data.

Parameters:

(Iterable, default:[]) –

set of edge tuples (i, j)

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)

Return a list of all possible node pairs in the graph.

Return the dictionary of node coordinates.

Return the dictionary of edge weights.

Check if the graph has coordinates.

Requires all nodes to have coordinates.

Check if the graph has edge weights.

Requires all edges to have a weight.

Check if the graph has edges.

Check if the graph has node weights.

Requires all nodes to have a weight.

Return the dictionary of node weights.

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)) -&gt; DataGraph classmethod ”

Constructs a circle graph, with the respective coordinates.

Parameters:

(int) –

number of nodes.

(float, default:1.0) –

distance between each node.

(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
@classmethod
def 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) -&gt; 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:

(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) -&gt; 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.

(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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of coordinates.

Parameters:

(list | dict) –

list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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) -&gt; 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
@classmethod
def 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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of nodes.

Parameters:

(Iterable) –

set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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

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
@classmethod
def 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) -&gt; 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 (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.

Parameters:

(Data) –

PyTorch Geometric Data object to convert.

(Iterable[str] | None, default:None) –

extra node attributes to copy (beyond x and pos).

(Iterable[str] | None, default:None) –

extra edge attributes to copy (beyond edge_attr).

(Iterable[str] | None, default:None) –

extra graph-level attributes to copy (beyond y).

(str | None, default:None) –

Data attribute to use as node weights.

(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_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.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of hexagons.

(int) –

Number of columns of hexagons.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Notes
Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of hexagons.

(int) –

Number of columns of hexagons.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

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()}

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) -&gt; DataGraph classmethod ”

Constructs a line graph, with the respective coordinates.

Parameters:

(int) –

number of nodes.

(float, default:1.0) –

distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; float ”

Returns the maximum distance in the graph.

Parameters:

(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) -&gt; float ”

Returns the minimum distance in the graph.

Parameters:

(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) -&gt; 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
@classmethod
def 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) -&gt; 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.

(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
@classmethod
def 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) -&gt; None ”

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

(float | None, default:None) –

value to scale by.

(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.")

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) -&gt; DataGraph classmethod ”

Constructs a square lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of square.

(int) –

Number of columns of square.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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 = &#39;weight&#39;, edge_weights_attr: str = &#39;edge_weight&#39;) -&gt; 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:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

(Iterable[str] | None, default:None) –

extra node attributes to export (beyond x).

(Iterable[str] | None, default:None) –

extra edge attributes to export (beyond edge_attr).

(Iterable[str] | None, default:None) –

extra graph-level attributes to export (beyond y).

(str, default:'weight') –

Data attribute name for node weights. Defaults to "weight".

(str, default:'edge_weight') –

Data attribute name for edge weights. Defaults to "edge_weight".

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is 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) -&gt; DataGraph classmethod ”

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of triangles.

(int) –

Number of columns of triangles.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

(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.")

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:

(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)

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:

(BaseDevice) –

a BaseDevice to build the QoolQit device from.

(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 PasqalCloud
from qoolqit import Device
# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device
# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
VirtualAnalog,
max_radial_distance=100,
max_sequence_duration=7000
)
# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

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()

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0)

Section titled “ Drive(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0) ”

The drive Hamiltonian acting over a duration.

Must be instantiated with keyword arguments. Accepts either an amplitude waveform, a detuning waveform, or both. A phase value can also be passed.

Parameters:

(Waveform | None, default:None) –

waveform representing Ω(t) in the drive Hamiltonian.

(Waveform | None, default:None) –

waveform representing δ(t) in the drive Hamiltonian.

(float, default:0.0) –

phase value ɸ for the amplitude term.

(list[WeightedDetuning] | None, default:None) –

additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.

Attributes:

Source code in qoolqit/drive.py
def __init__(
self,
*args: Any,
amplitude: Waveform | None = None,
detuning: Waveform | None = None,
weighted_detunings: list[WeightedDetuning] | None = None,
phase: float = 0.0,
) -> None:
"""Default constructor for the Drive.
Must be instantiated with keyword arguments. Accepts either an amplitude waveform,
a detuning waveform, or both. A phase value can also be passed.
Arguments:
amplitude: waveform representing Ω(t) in the drive Hamiltonian.
detuning: waveform representing δ(t) in the drive Hamiltonian.
phase: phase value ɸ for the amplitude term.
weighted_detunings: additional waveforms and weights applied to individual
qubits. Note that these detunings are not supported on all devices.
"""
if len(args) > 0:
raise TypeError("Please pass the `amplitude` and / or `detuning` as keyword arguments.")
if amplitude is None and detuning is None:
raise ValueError("Amplitude and detuning cannot both be empty.")
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.")
self._amplitude: Waveform
self._detuning: Waveform
self._amplitude_orig: Waveform
self._detuning_orig: Waveform
if amplitude is None and isinstance(detuning, Waveform):
self._amplitude = Delay(detuning.duration)
self._detuning = detuning
elif detuning is None and isinstance(amplitude, Waveform):
self._amplitude = amplitude
self._detuning = Delay(amplitude.duration)
elif isinstance(detuning, Waveform) and isinstance(amplitude, Waveform):
self._amplitude = amplitude
self._detuning = detuning
if self._amplitude.min() < 0.0:
raise ValueError("Amplitude cannot be negative.")
self._amplitude_orig = self._amplitude
self._detuning_orig = self._detuning
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
self._phase = phase
self._weighted_detunings = weighted_detunings if weighted_detunings is not None else []

The amplitude waveform in the drive.

The detuning waveform in the drive.

The phase value in the drive.

weighted_detunings: Sequence[WeightedDetuning] property

Section titled “ weighted_detunings: Sequence[WeightedDetuning] property ”

Detunings applied to individual qubits.

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 = &#39;PchipInterpolator&#39;, **interpolator_kwargs: Any) ”

A waveform created from interpolation of a set of data points.

Parameters:

(int) –

The waveform duration (in ns).

(ArrayLike) –

Values of the interpolation points. Must be a list of castable to float or a parametrized object.

(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.

(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)

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.

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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:

(list | tuple) –

list or tuple of N duration 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)

Returns the duration of the waveform.

Returns the list of durations of each individual waveform.

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.

Returns the list of times when each individual waveform starts.

Returns a list of the individual waveforms.

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

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])

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) –

the register of qubits, defining their positions.

(Drive) –

the drive acting on qubits, defining amplitude, detuning and phase.

Methods:

  • compile_to

    Compiles the quantum program for execution on a specific device.

Attributes:

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.")
self._drive = drive
self._compiled_sequence: PulserSequence | None = None
for detuning in drive.weighted_detunings:
for key in detuning.weights.keys():
if key not in register.qubits:
raise ValueError(
"In this QuantumProgram, the drive and the register "
f"do not match: qubit {key} appears in the drive but "
"is not defined in the register."
)

compiled_sequence: PulserSequence property

Section titled “ compiled_sequence: PulserSequence property ”

The Pulser sequence compiled to a specific device.

The driving waveforms.

Check if the program has been compiled.

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) -&gt; 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) –

The target device for compilation.

(CompilerProfile, default:MAX_ENERGY) –

The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.

(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."
)
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:

(float) –

the total duration.

(float) –

the initial value at t = 0.

(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)

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.

The Register in QoolQit, representing a set of qubits with coordinates.

Parameters:

(dict) –

a dictionary of qubits and respective coordinates {q: (x, y), ...}.

Methods:

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

Number of qubits in the Register.

Returns the dictionary of qubits and respective coordinates.

Returns the qubit keys.

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) -&gt; Figure | None ”

Draw the register.

Parameters:

(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) -&gt; Register classmethod ”

Initializes a Register from a list of coordinates.

Parameters:

(list) –

a list of coordinates [(x, y), ...]

Source code in qoolqit/register.py
@classmethod
def 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) -&gt; Register classmethod ”

Initializes a Register from a graph that has coordinates.

Parameters:

(DataGraph) –

a DataGraph instance.

Source code in qoolqit/register.py
@classmethod
def 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)

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()}

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

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 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) –

the QoolQit Register.

(Drive) –

the QoolQit Drive.

(Device) –

the QoolQit Device.

(CompilerProfile) –

the CompilerProfile to use.

(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:

(float) –

the total duration.

(float, default:1.0) –

the amplitude of the sine wave.

(float, default:1.0) –

the frequency of the sine wave.

(float, default:0.0) –

the phase of the sine wave.

(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)

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.

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)

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)

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()

Modules:

Classes:

  • AnalogDevice

    A realistic device for analog sequence execution.

  • Device

    QoolQit Device wrapper around a Pulser BaseDevice.

  • DigitalAnalogDevice

    A device with digital and analog capabilities.

  • MockDevice

    A virtual device for unconstrained prototyping.

Functions:

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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:

(BaseDevice) –

a BaseDevice to build the QoolQit device from.

(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 PasqalCloud
from qoolqit import Device
# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device
# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
VirtualAnalog,
max_radial_distance=100,
max_sequence_duration=7000
)
# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

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()

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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:

(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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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()

Classes:

  • AnalogDevice

    A realistic device for analog sequence execution.

  • Device

    QoolQit Device wrapper around a Pulser BaseDevice.

  • DigitalAnalogDevice

    A device with digital and analog capabilities.

  • MockDevice

    A virtual device for unconstrained prototyping.

Functions:

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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 (RemoteConnection) –

    connection object to fetch the available devices.

  • name (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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)
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:

(BaseDevice) –

a BaseDevice to build the QoolQit device from.

(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 PasqalCloud
from qoolqit import Device
# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device
# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
VirtualAnalog,
max_radial_distance=100,
max_sequence_duration=7000
)
# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

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()

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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 (RemoteConnection) –

    connection object to fetch the available devices.

  • name (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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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 (RemoteConnection) –

    connection object to fetch the available devices.

  • name (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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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)

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod
Section titled “ from_connection(connection: RemoteConnection, name: str) -&gt; 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 (RemoteConnection) –

    connection object to fetch the available devices.

  • name (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 PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
Source code in qoolqit/devices/device.py
@classmethod
def 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)

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)

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

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)

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)

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()

Classes:

  • UnitConverter

    A dataclass representing a unit converter in the Rydberg-Analog model.

UnitConverter(C6: float, time: float, energy: float, distance: float) dataclass
Section titled “ UnitConverter(C6: float, time: float, energy: float, distance: float) dataclass ”

A dataclass representing a unit converter in the Rydberg-Analog model.

Includes three inter-dependent factors for TIME, ENERGY and DISTANCE conversion, also depending on the interaction coefficient C6. The converter checks the following invariants, based on the units used by Pulser:

Conversion invariants: 1. TIME * ENERGY = 1000 ( <=> TIME = 1000 / ENERGY ) 2. DISTANCE^6 * ENERGY = C6 ( <=> ENERGY = C6 / (DISTANCE ^ 6) )

Methods:

  • factors_from_distance

    Get factors from a different reference distance than the one set.

  • factors_from_energy

    Get factors from a different reference energy than the one set.

  • factors_from_time

    Get factors from a different reference time than the one set.

  • from_distance

    Instantiate from a reference C6 value and a reference distance unit.

  • from_energy

    Instantiate from a reference C6 value and a reference energy unit.

  • from_time

    Instantiate from a reference C6 value and a reference time unit.

  • validate_factors

    Returns True if the conversion invariants are respected.

Attributes:

  • C6 (float) –

    Time conversion factor.

  • energy (float) –

    Distance conversion factor.

  • factors (tuple[float, ...]) –

    Return the current conversion factors set.

  • time (float) –

    Energy conversion factor.

C6: float = field(repr=False) class-attribute instance-attribute
Section titled “ C6: float = field(repr=False) class-attribute instance-attribute ”

Time conversion factor.

Distance conversion factor.

factors: tuple[float, ...] property writable
Section titled “ factors: tuple[float, ...] property writable ”

Return the current conversion factors set.

Energy conversion factor.

factors_from_distance(distance: float) -> tuple[float, ...]
Section titled “ factors_from_distance(distance: float) -&gt; tuple[float, ...] ”

Get factors from a different reference distance than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_distance(self, distance: float) -> tuple[float, ...]:
"""Get factors from a different reference distance than the one set."""
return _factors_from_distance(self.C6, distance)
factors_from_energy(energy: float) -> tuple[float, ...]
Section titled “ factors_from_energy(energy: float) -&gt; tuple[float, ...] ”

Get factors from a different reference energy than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_energy(self, energy: float) -> tuple[float, ...]:
"""Get factors from a different reference energy than the one set."""
return _factors_from_energy(self.C6, energy)
factors_from_time(time: float) -> tuple[float, ...]
Section titled “ factors_from_time(time: float) -&gt; tuple[float, ...] ”

Get factors from a different reference time than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_time(self, time: float) -> tuple[float, ...]:
"""Get factors from a different reference time than the one set."""
return _factors_from_time(self.C6, time)
from_distance(C6: float, distance: float) -> UnitConverter classmethod
Section titled “ from_distance(C6: float, distance: float) -&gt; UnitConverter classmethod ”

Instantiate from a reference C6 value and a reference distance unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_distance(cls, C6: float, distance: float) -> UnitConverter:
"""Instantiate from a reference C6 value and a reference distance unit."""
time, energy, distance = _factors_from_distance(C6, distance)
return UnitConverter(C6, time, energy, distance)
from_energy(C6: float, energy: float) -> UnitConverter classmethod
Section titled “ from_energy(C6: float, energy: float) -&gt; UnitConverter classmethod ”

Instantiate from a reference C6 value and a reference energy unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_energy(cls, C6: float, energy: float) -> UnitConverter:
"""Instantiate from a reference C6 value and a reference energy unit."""
time, energy, distance = _factors_from_energy(C6, energy)
return UnitConverter(C6, time, energy, distance)
from_time(C6: float, time: float) -> UnitConverter classmethod
Section titled “ from_time(C6: float, time: float) -&gt; UnitConverter classmethod ”

Instantiate from a reference C6 value and a reference time unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_time(cls, C6: float, time: float) -> UnitConverter:
"""Instantiate from a reference C6 value and a reference time unit."""
time, energy, distance = _factors_from_time(C6, time)
return UnitConverter(C6, time, energy, distance)
validate_factors(time: float, energy: float, distance: float) -> bool
Section titled “ validate_factors(time: float, energy: float, distance: float) -&gt; bool ”

Returns True if the conversion invariants are respected.

Source code in qoolqit/devices/unit_converter.py
def validate_factors(self, time: float, energy: float, distance: float) -> bool:
"""Returns True if the conversion invariants are respected."""
time_energy_inv = time * energy
energy_dist_inv = (distance**6) * energy
return isclose(time_energy_inv, 1000.0) and isclose(energy_dist_inv, self.C6)

Classes:

Drive(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0)

Section titled “ Drive(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0) ”

The drive Hamiltonian acting over a duration.

Must be instantiated with keyword arguments. Accepts either an amplitude waveform, a detuning waveform, or both. A phase value can also be passed.

Parameters:

(Waveform | None, default:None) –

waveform representing Ω(t) in the drive Hamiltonian.

(Waveform | None, default:None) –

waveform representing δ(t) in the drive Hamiltonian.

(float, default:0.0) –

phase value ɸ for the amplitude term.

(list[WeightedDetuning] | None, default:None) –

additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.

Attributes:

Source code in qoolqit/drive.py
def __init__(
self,
*args: Any,
amplitude: Waveform | None = None,
detuning: Waveform | None = None,
weighted_detunings: list[WeightedDetuning] | None = None,
phase: float = 0.0,
) -> None:
"""Default constructor for the Drive.
Must be instantiated with keyword arguments. Accepts either an amplitude waveform,
a detuning waveform, or both. A phase value can also be passed.
Arguments:
amplitude: waveform representing Ω(t) in the drive Hamiltonian.
detuning: waveform representing δ(t) in the drive Hamiltonian.
phase: phase value ɸ for the amplitude term.
weighted_detunings: additional waveforms and weights applied to individual
qubits. Note that these detunings are not supported on all devices.
"""
if len(args) > 0:
raise TypeError("Please pass the `amplitude` and / or `detuning` as keyword arguments.")
if amplitude is None and detuning is None:
raise ValueError("Amplitude and detuning cannot both be empty.")
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.")
self._amplitude: Waveform
self._detuning: Waveform
self._amplitude_orig: Waveform
self._detuning_orig: Waveform
if amplitude is None and isinstance(detuning, Waveform):
self._amplitude = Delay(detuning.duration)
self._detuning = detuning
elif detuning is None and isinstance(amplitude, Waveform):
self._amplitude = amplitude
self._detuning = Delay(amplitude.duration)
elif isinstance(detuning, Waveform) and isinstance(amplitude, Waveform):
self._amplitude = amplitude
self._detuning = detuning
if self._amplitude.min() < 0.0:
raise ValueError("Amplitude cannot be negative.")
self._amplitude_orig = self._amplitude
self._detuning_orig = self._detuning
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
self._phase = phase
self._weighted_detunings = weighted_detunings if weighted_detunings is not None else []

The amplitude waveform in the drive.

The detuning waveform in the drive.

The phase value in the drive.

weighted_detunings: Sequence[WeightedDetuning] property
Section titled “ weighted_detunings: Sequence[WeightedDetuning] property ”

Detunings applied to individual qubits.

WeightedDetuning(weights: dict[Any, float], waveform: Waveform) dataclass

Section titled “ WeightedDetuning(weights: dict[Any, float], waveform: Waveform) dataclass ”

A weighted detuning.

See https://pasqal-io.github.io/qoolqit/latest/theory/rydberg_model/#weighted-detuning (external) for details on weighted detunings.

Note: detuning with positive waveforms cannot be instantiated.

Attributes:

  • waveform (Waveform) –

    The waveform for this detuning.

  • weights (dict[Any, float]) –

    Association of weights to qubits.

The waveform for this detuning.

In the companion documentation, this is the function Delta(t).

weights: dict[Any, float] instance-attribute
Section titled “ weights: dict[Any, float] instance-attribute ”

Association of weights to qubits.

Each weight must be in [0, 1], where 0 means that the waveform is ignored for this qubit and 1 means that the waveform is fully applied to this qubit.

In the companion documentation, these are the value epsilon_i.

Collection of graph and matrix embedding algorithms.

Modules:

Classes:

BaseEmbedder(algorithm: Callable, config: ConfigType)

Section titled “ BaseEmbedder(algorithm: Callable, config: ConfigType) ”

Abstract base class for all embedders.

An embedder is a function that maps a InDataType to an OutDataType through an embedding algorithm. Parameters of the embedding algorithm can be customized through the EmbedderConfig.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

  • validate_input

    Checks if the given data is compatible with the embedder.

  • validate_output

    Checks if the resulting output is expected by the embedder.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(InDataType) –

the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result
validate_input(data: InDataType) -> None abstractmethod
Section titled “ validate_input(data: InDataType) -&gt; None abstractmethod ”

Checks if the given data is compatible with the embedder.

Each embedder should write its own data validator. If the data is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

(InDataType) –

the data to validate.

Raises:

  • TypeError

    if the data is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_input(self, data: InDataType) -> None:
"""Checks if the given data is compatible with the embedder.
Each embedder should write its own data validator. If the data
is not of the supported type or in the specific supported format
for that embedder, an error should be raised.
Arguments:
data: the data to validate.
Raises:
TypeError: if the data is not of the supported type.
SomeError: some other error if other constraints are not met.
"""
...
validate_output(result: OutDataType) -> None abstractmethod
Section titled “ validate_output(result: OutDataType) -&gt; None abstractmethod ”

Checks if the resulting output is expected by the embedder.

Each embedder should write its own output validator. If the result is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

(OutDataType) –

the output to validate.

Raises:

  • TypeError

    if the output is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_output(self, result: OutDataType) -> None:
"""Checks if the resulting output is expected by the embedder.
Each embedder should write its own output validator. If the result
is not of the supported type or in the specific supported format
for that embedder, an error should be raised.
Arguments:
result: the output to validate.
Raises:
TypeError: if the output is not of the supported type.
SomeError: some other error if other constraints are not met.
"""
...

Blade(config: BladeConfig = BladeConfig())

Section titled “ Blade(config: BladeConfig = BladeConfig()) ”

A matrix to graph embedder using the BLaDE algorithm.

Parameters:

(BladeConfig, default:BladeConfig()) –

configuration object for the BLaDE algorithm.

Methods:

  • embed

    Return a DataGraph with coordinates that embeds the input matrix.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self, config: BladeConfig = BladeConfig()) -> None:
"""Inits Blade.
Args:
config (BladeConfig): configuration object for the BLaDE algorithm.
"""
super().__init__(blade, config=config)

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Return a DataGraph with coordinates that embeds the input matrix.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(ndarray) –

the matrix to embed into a DataGraph with coordinates.

Source code in qoolqit/embedding/matrix_embedder.py
def embed(self, data: np.ndarray) -> DataGraph:
"""Return a DataGraph with coordinates that embeds the input matrix.
Validates the input, runs the embedding algorithm, and validates the output.
Args:
data (np.ndarray): the matrix to embed into a DataGraph with coordinates.
"""
self.validate_input(data)
positions = self.algorithm(data, **self.config.dict())
graph = DataGraph.from_coordinates(positions.tolist())
return graph

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass

Section titled “ BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass ”

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

(Device) –

the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
"""Post initialization of the `BladeConfig` dataclass.
Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
based on the specification of the selected device.
Args:
device (Device): the QoolQit device to use to set the maximum ratio between the maximum
radial distance and the minimum pairwise distance between atoms.
"""
if device:
if self.max_min_dist_ratio:
logger.warning(
"`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
)
min_distance = device._min_distance
max_radial_distance = device._max_radial_distance
if max_radial_distance and min_distance:
self.max_min_dist_ratio = max_radial_distance / min_distance

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

Base abstract dataclass for all embedding algorithm configurations.

Subclasses define parameters specific to their algorithms. Each config should define fields that directly translate to arguments in the respective embedding function it configures.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

GraphToGraphEmbedder(algorithm: Callable, config: ConfigType)

Section titled “ GraphToGraphEmbedder(algorithm: Callable, config: ConfigType) ”

A family of embedders that map a graph to a graph.

Focused on unit-disk graph embedding, where the goal is to find a set of coordinates for a graph that has no coordinates, such that the final unit-disk edges matches the set of edges in the original graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(InDataType) –

the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

A matrix to graph embedder using the interaction embedding algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self) -> None:
super().__init__(interaction_embedding, InteractionEmbedderConfig())

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(InDataType) –

the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass

Section titled “ InteractionEmbedderConfig(method: str = &#39;Nelder-Mead&#39;, maxiter: int = 200000, tol: float = 1e-08) dataclass ”

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType)

Section titled “ MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType) ”

A family of embedders that map a matrix to a graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(InDataType) –

the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass

Section titled “ SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass ”

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig())

Section titled “ SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig()) ”

A graph to graph embedder using the spring layout algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/graph_embedder.py
def __init__(self, config: SpringLayoutConfig = SpringLayoutConfig()) -> None:
"""Inits SpringLayoutEmbedder."""
super().__init__(spring_layout_embedding, config=config)

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

(InDataType) –

the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

Modules:

Classes:

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass
Section titled “ BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass ”

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
"""Post initialization of the `BladeConfig` dataclass.
Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
based on the specification of the selected device.
Args:
device (Device): the QoolQit device to use to set the maximum ratio between the maximum
radial distance and the minimum pairwise distance between atoms.
"""
if device:
if self.max_min_dist_ratio:
logger.warning(
"`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
)
min_distance = device._min_distance
max_radial_distance = device._max_radial_distance
if max_radial_distance and min_distance:
self.max_min_dist_ratio = max_radial_distance / min_distance

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass
Section titled “ InteractionEmbedderConfig(method: str = &#39;Nelder-Mead&#39;, maxiter: int = 200000, tol: float = 1e-08) dataclass ”

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)
SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass
Section titled “ SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass ”

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

Modules:

Classes:

  • BladeConfig

    Configuration parameters to embed with BLaDE.

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass
Section titled “ BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass ”

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
"""Post initialization of the `BladeConfig` dataclass.
Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
based on the specification of the selected device.
Args:
device (Device): the QoolQit device to use to set the maximum ratio between the maximum
radial distance and the minimum pairwise distance between atoms.
"""
if device:
if self.max_min_dist_ratio:
logger.warning(
"`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
)
min_distance = device._min_distance
max_radial_distance = device._max_radial_distance
if max_radial_distance and min_distance:
self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

Classes:

  • BladeConfig

    Configuration parameters to embed with BLaDE.

Functions:

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None)
dataclass

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
"""Post initialization of the `BladeConfig` dataclass.
Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
based on the specification of the selected device.
Args:
device (Device): the QoolQit device to use to set the maximum ratio between the maximum
radial distance and the minimum pairwise distance between atoms.
"""
if device:
if self.max_min_dist_ratio:
logger.warning(
"`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
)
min_distance = device._min_distance
max_radial_distance = device._max_radial_distance
if max_radial_distance and min_distance:
self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)
blade(matrix: np.ndarray, *, max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = default_compute_max_distance_to_walk, compute_regulation_cursor: Callable[[float], float] = lambda _: 0.1, compute_ratio_step_factors: Callable[[float], float] = default_compute_ratio_step_factors, ratio_rerun: int = 2, draw_steps: bool | list[int] = False, draw_weighted_graph: bool = False, draw_differences: bool = False) -> np.ndarray

Embed an interaction matrix or QUBO with the BLaDE algorithm.

BLaDE stands for Balanced Latently Dimensional Embedder. It compute positions for nodes so that their interactions approach the desired values. The interactions assume that the interaction coefficient of the device is set to 1. Its typical target is on interaction matrices or QUBOs, but it can also be used for MIS with limitations if the adjacency matrix is converted into a QUBO. The general principle is based on the Fruchterman-Reingold algorithm.

An objective interaction matrix or QUBO between the nodes. It must

max_min_dist_ratio: If present, set the maximum ratio between the maximum radial distance and the minimum pairwise distances. dimensions: List of numbers of dimensions to explore one after the other. A list with one value is equivalent to a list containing twice the same value. For a 2D embedding, the last value should be 2. Increasing the number of intermediate dimensions can help to escape from local minima. starting_positions: If provided, initial positions to start from. Otherwise, random positions will be generated. The number of dimensions of the starting positions must be lower than or equal to the first dimension to explore. If it is lower, it is added dimensions filled with random values. pca: Whether to apply Principal Component Analysis to prioritize dimensions to keep when transitioning from a space to a space with fewer dimensions. It is disabled by default because it can raise an error when there are too many dimensions compared to the number of nodes. steps_per_round: Number of elementary steps to perform for each dimension transition, where at each step move vectors are computed and applied on the nodes. compute_weight_relative_threshold: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps. It must return a float number between 0 and 1 that gives a threshold determining which weights are significant (see update_positions to learn more). compute_max_distance_to_walk: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps, and takes another argument that is set to None when max_min_dist_ratio is not enabled, otherwise, it is set to the maximum radial distance for the current step. It must return a float number that limits the distances nodes can move at one step (see update_positions to learn more). compute_regulation_cursor: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps. It must return a float number between 0 (no regulation) and 1 (full regulation) that uniformizes the ability for the forces to achieve their objectives at each step by changing priorities. ratio_rerun: When the distance ratio constraint is not met, it defines the maximum number of times the algorithm applies additional computation steps putting the priority on the constraint. compute_ratio_step_factors: Function that is called at the boundaries of the rounds. It defines the target ratio the enforce during the evolution. It acts as a multiplying factor on the target ratio. draw_steps: If it is a boolean, it defines whether to globally enable drawing and traces for nodes and forces (for all steps). If it is a list of integers, it defines a subset of steps to enable such drawing. Requires installing the seaborn library. draw_weighted_graph: For each step with drawing enabled, defines whether to draw a weighted graph representing interactions. draw_differences: For each step with drawing enabled, defines whether to draw the differences between current and target interactions.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def blade(
matrix: np.ndarray,
*,
max_min_dist_ratio: float | None = None,
dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2),
starting_positions: np.ndarray | None = None,
pca: bool = False,
steps_per_round: int = 200,
compute_weight_relative_threshold: Callable[[float], float] = (lambda _: 0.1),
compute_max_distance_to_walk: Callable[
[float, float | None], float | tuple[float, float, float]
] = default_compute_max_distance_to_walk,
compute_regulation_cursor: Callable[[float], float] = (lambda _: 0.1),
compute_ratio_step_factors: Callable[[float], float] = default_compute_ratio_step_factors,
ratio_rerun: int = 2,
draw_steps: bool | list[int] = False,
draw_weighted_graph: bool = False,
draw_differences: bool = False,
) -> np.ndarray:
"""
Embed an interaction matrix or QUBO with the BLaDE algorithm.
BLaDE stands for Balanced Latently Dimensional Embedder.
It compute positions for nodes so that their interactions
approach the desired values. The interactions assume that the
interaction coefficient of the device is set to 1.
Its typical target is on interaction matrices or QUBOs, but it can also be used
for MIS with limitations if the adjacency matrix is converted into a QUBO.
The general principle is based on the Fruchterman-Reingold algorithm.
matrix: An objective interaction matrix or QUBO between the nodes. It must
be either symmetrical or triangular.
max_min_dist_ratio: If present, set the maximum ratio between
the maximum radial distance and the minimum pairwise distances.
dimensions: List of numbers of dimensions to explore one
after the other. A list with one value is equivalent to a list containing
twice the same value. For a 2D embedding, the last value should be 2.
Increasing the number of intermediate dimensions can help to escape
from local minima.
starting_positions: If provided, initial positions to start from. Otherwise,
random positions will be generated. The number of dimensions of the
starting positions must be lower than or equal to the first dimension
to explore. If it is lower, it is added dimensions filled with
random values.
pca: Whether to apply Principal Component Analysis to prioritize dimensions
to keep when transitioning from a space to a space with fewer dimensions.
It is disabled by default because it can raise an error when there are
too many dimensions compared to the number of nodes.
steps_per_round: Number of elementary steps to perform for each dimension
transition, where at each step move vectors are computed and applied
on the nodes.
compute_weight_relative_threshold: Function that is called at each step.
It takes a float number between 0 and 1 that represents the progress
on the steps. It must return a float number between 0 and 1 that gives
a threshold determining which weights are significant (see
`update_positions` to learn more).
compute_max_distance_to_walk: Function that is called at each step.
It takes a float number between 0 and 1 that represents the progress
on the steps, and takes another argument that is set to `None` when
`max_min_dist_ratio` is not enabled, otherwise, it is set to
the maximum radial distance for the current step.
It must return a float number that limits the distances
nodes can move at one step (see `update_positions` to learn more).
compute_regulation_cursor: Function that is called at each step.
It takes a float number between 0 and 1 that represents the progress
on the steps. It must return a float number between 0 (no regulation)
and 1 (full regulation) that uniformizes the ability for the forces
to achieve their objectives at each step by changing priorities.
ratio_rerun: When the distance ratio constraint is not met, it defines
the maximum number of times the algorithm applies additional
computation steps putting the priority on the constraint.
compute_ratio_step_factors: Function that is called at the boundaries of
the rounds. It defines the target ratio the enforce during the
evolution. It acts as a multiplying factor on the target ratio.
draw_steps: If it is a boolean, it defines whether to globally enable
drawing and traces for nodes and forces (for all steps). If it is a
list of integers, it defines a subset of steps to enable such drawing.
Requires installing the seaborn library.
draw_weighted_graph: For each step with drawing enabled, defines whether
to draw a weighted graph representing interactions.
draw_differences: For each step with drawing enabled, defines whether
to draw the differences between current and target interactions.
"""
if len(dimensions) == 1:
dimensions = (dimensions[0], dimensions[0])
assert len(dimensions) >= 2
if isinstance(matrix, np.ndarray):
assert not np.all(matrix == 0)
else:
assert not torch.all(matrix == 0)
graph = Qubo.from_matrix(matrix).as_graph()
matrix = np.array(
nx.adjacency_matrix(graph, nodelist=list(range(len(matrix))), weight="weight").toarray()
)
if starting_positions is None:
positions = generate_random_positions(target_interactions=matrix, dimension=dimensions[0])
elif starting_positions.shape[1] <= dimensions[0]:
positions = augment_dimensions_with_random_values(
starting_positions, new_dimensions=dimensions[0] - starting_positions.shape[1]
)
else:
raise ValueError(
f"The number of dimensions in the starting positions "
f"{starting_positions.shape[1]} is greater than the starting "
f"number of dimensions {dimensions[0]}."
)
total_steps = steps_per_round * (len(dimensions) - 1)
def step_to_progress(step: int) -> float:
return step / (total_steps - 1)
steps_ratios: list[float | None] = []
if max_min_dist_ratio is not None:
steps_ratios = [
max_min_dist_ratio * compute_ratio_step_factors(progress)
for progress in np.linspace(0, 1, len(dimensions))
]
starting_min = _compute_min_pairwise_distance(positions)
else:
steps_ratios = [None] * len(dimensions)
starting_min = None
assert len(dimensions) == len(steps_ratios)
def compute_weight_relative_threshold_by_step(step: int) -> float:
return compute_weight_relative_threshold(step_to_progress(step))
def compute_max_distance_to_walk_by_step(
step: int, max_radial_dist: float | None
) -> float | tuple[float, float, float]:
return compute_max_distance_to_walk(step_to_progress(step), max_radial_dist)
def compute_regulation_cursor_by_step(step: int) -> float:
return compute_regulation_cursor(step_to_progress(step))
for dim_idx, start_ratio, final_ratio in zip(
range(len(dimensions) - 1), steps_ratios[:-1], steps_ratios[1:]
):
positions, starting_min = evolve_with_dimension_transition(
target_interactions=matrix,
dimensions=dimensions,
starting_min=starting_min,
pca=pca,
start_step=dim_idx * steps_per_round,
stop_step=(dim_idx + 1) * steps_per_round,
compute_weight_relative_threshold_by_step=compute_weight_relative_threshold_by_step,
compute_max_distance_to_walk_by_step=compute_max_distance_to_walk_by_step,
compute_regulation_cursor_by_step=compute_regulation_cursor_by_step,
positions=positions,
final_ratio=final_ratio,
dim_idx=dim_idx,
start_ratio=start_ratio,
draw_steps=draw_steps,
draw_weighted_graph=draw_weighted_graph,
draw_differences=draw_differences,
)
if max_min_dist_ratio is not None:
max_radial_dist = max(np.linalg.norm(positions, axis=-1))
min_atom_dist = _compute_min_pairwise_distance(positions)
output_ratio = max_radial_dist / min_atom_dist
if output_ratio > max_min_dist_ratio:
if ratio_rerun > 0:
return blade(
matrix=matrix,
max_min_dist_ratio=max_min_dist_ratio,
dimensions=(2, 2, 2),
starting_positions=positions,
steps_per_round=steps_per_round,
compute_max_distance_to_walk=lambda x, max_radial_dist: 0,
compute_ratio_step_factors=lambda progress: np.interp(
progress, xp=[0, 1 / 2, 1], fp=[0.8, 0.9, 0.98]
),
ratio_rerun=ratio_rerun - 1,
)
print(
f"[Warning] Output ratio {output_ratio}"
f" is higher than required {max_min_dist_ratio}"
)
return positions
default_compute_max_distance_to_walk(progress: float, max_radial_dist: float | None) -> float

Default function with rapid then slow decrease to zero of the walking distance.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def default_compute_max_distance_to_walk(progress: float, max_radial_dist: float | None) -> float:
"""Default function with rapid then slow decrease to zero of the walking distance."""
if max_radial_dist is None:
return float(np.inf)
return float(2 * max_radial_dist * (1 - np.sin(np.pi / 2 * progress)))
default_compute_ratio_step_factors(progress: float) -> float

Default function to decrease the ratio slightly too low and then increase.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def default_compute_ratio_step_factors(progress: float) -> float:
"""Default function to decrease the ratio slightly too low and then increase."""
return float(np.interp(progress, xp=[0, 3 / 5, 1], fp=[2, 0.94, 0.98]))
update_positions(*, positions: np.ndarray, target_interactions: np.ndarray, weight_relative_threshold: float = 0.0, min_dist: float | None = None, max_radius: float | None = None, max_distance_to_walk: float | tuple[float, float, float] = np.inf, regulation_cursor: float = 0.0, draw_step: bool = False, step: int | None = None, draw_weighted_graph: bool = False) -> np.ndarray

Compute vector moves to adjust node positions toward target interactions.

positions: Starting positions of the nodes. target_interactions: Desired interactions. weight_relative_threshold: It is used to compute a weight difference threshold defining which weights differences are significant and should be considered. For this purpose, it is multiplied by the higher weight difference. It is also used to reduce the precision when targeting the objective weights. min_dist: If set, defined the minimum distance that should be met, and creates forces to enforce the constraint. max_radius: If set, defined the maximum radius that should be met, and creates forces to enforce the constraint. max_distance_to_walk: It set, limits the distance that nodes can walk when the forces are applied. It impacts the priorities of the forces because they only consider the slope of the differences in weights that can be targeting with this ceiling. regulation_cursor: A cursor between 0 (no regulation) and 1 (full regulation) to uniformize the ability of the forces to achieve their interaction targets. draw_step: Whether to draw the nodes and the forces. step: Step number.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def update_positions(
*,
positions: np.ndarray,
target_interactions: np.ndarray,
weight_relative_threshold: float = 0.0,
min_dist: float | None = None,
max_radius: float | None = None,
max_distance_to_walk: float | tuple[float, float, float] = np.inf,
regulation_cursor: float = 0.0,
draw_step: bool = False,
step: int | None = None,
draw_weighted_graph: bool = False,
) -> np.ndarray:
"""
Compute vector moves to adjust node positions toward target interactions.
positions: Starting positions of the nodes.
target_interactions: Desired interactions.
weight_relative_threshold: It is used to compute a weight difference
threshold defining which weights differences are significant and should
be considered. For this purpose, it is multiplied by the higher weight difference.
It is also used to reduce the precision when targeting
the objective weights.
min_dist: If set, defined the minimum distance that should be met, and
creates forces to enforce the constraint.
max_radius: If set, defined the maximum radius that should be met, and
creates forces to enforce the constraint.
max_distance_to_walk: It set, limits the distance that nodes can walk
when the forces are applied. It impacts the priorities
of the forces because they only consider the slope of the differences
in weights that can be targeting with this ceiling.
regulation_cursor: A cursor between 0 (no regulation) and 1 (full
regulation) to uniformize the ability of the forces to achieve their
interaction targets.
draw_step: Whether to draw the nodes and the forces.
step: Step number.
"""
if draw_step:
print(f"{weight_relative_threshold=}")
print(f"{regulation_cursor=}")
assert np.array_equal(target_interactions, target_interactions.T)
n = len(target_interactions)
positions = np.array(positions, dtype=float)
nb_positions, space_dimension = positions.shape
if isinstance(max_distance_to_walk, tuple):
max_distance_to_walk, min_constr_max_distance_to_walk, max_constr_max_distance_to_walk = (
max_distance_to_walk
)
else:
min_constr_max_distance_to_walk = np.inf
max_constr_max_distance_to_walk = np.inf
assert nb_positions == n
position_differences = positions[np.newaxis, :] - positions[:, np.newaxis]
distance_matrix = np.linalg.norm(position_differences, axis=2)
with np.errstate(divide="ignore", invalid="ignore"):
unitary_vectors = position_differences / distance_matrix[:, :, np.newaxis]
unitary_vectors[range(n), range(n)] = np.zeros(space_dimension)
logger.debug(f"{unitary_vectors=}")
modulated_target_interactions, interaction_force = compute_interaction_forces(
distance_matrix=distance_matrix,
unitary_vectors=unitary_vectors,
target_weights=target_interactions,
weight_relative_threshold=weight_relative_threshold,
max_distance_to_walk=max_distance_to_walk,
)
regulated_interaction_force = interaction_force.regulated(regulation_cursor=regulation_cursor)
if draw_step:
temp_ratio_before = np.max(np.triu(interaction_force.maximum_temperatures, k=1)) / np.min(
interaction_force.maximum_temperatures
)
print(f"temperature ratio before regulation: {temp_ratio_before}")
temp_ratio_after = np.max(
np.triu(regulated_interaction_force.maximum_temperatures, k=1)
) / np.min(regulated_interaction_force.maximum_temperatures)
print(f"temperature ratio after regulation: {temp_ratio_after}")
min_constr_force = compute_min_dist_constraint_forces(
min_dist=min_dist,
distance_matrix=distance_matrix,
unitary_vectors=unitary_vectors,
)
max_constr_force = compute_max_dist_constraint_forces(
positions=positions,
max_radius=max_radius,
)
interaction_resulting_forces = regulated_interaction_force.get_resulting_forces(
regulated_interaction_force.get_temperature()
)
min_constr_resulting_forces = min_constr_force.get_resulting_forces(
min_constr_force.get_temperature()
)
limited_min_constr_resulting_forces = (
min_constr_resulting_forces
* np.minimum(
1,
min_constr_max_distance_to_walk / np.linalg.norm(min_constr_resulting_forces, axis=-1),
)[:, np.newaxis]
)
limited_min_constr_resulting_forces[min_constr_resulting_forces == 0] = 0
max_constr_resulting_forces = max_constr_force.get_resulting_forces(
max_constr_force.get_temperature()
)
limited_max_constr_resulting_forces = (
max_constr_resulting_forces
* np.minimum(
1,
max_constr_max_distance_to_walk / np.linalg.norm(max_constr_resulting_forces, axis=-1),
)[:, np.newaxis]
)
limited_max_constr_resulting_forces[max_constr_resulting_forces == 0] = 0
resulting_forces_vectors = (
interaction_resulting_forces
+ limited_min_constr_resulting_forces
+ limited_max_constr_resulting_forces
)
logger.debug(f"{resulting_forces_vectors=}")
assert not np.any(np.isinf(interaction_resulting_forces)) and not np.any(
np.isnan(interaction_resulting_forces)
)
assert not np.any(np.isinf(min_constr_resulting_forces)) and not np.any(
np.isnan(min_constr_resulting_forces)
)
assert not np.any(np.isinf(max_constr_resulting_forces)) and not np.any(np.isnan(positions))
if draw_step:
draw_update_positions_step(
positions,
interaction_resulting_forces=interaction_resulting_forces,
min_constr_resulting_forces=min_constr_resulting_forces,
max_constr_resulting_forces=max_constr_resulting_forces,
resulting_forces_vectors=resulting_forces_vectors,
target_interactions=target_interactions,
current_interactions=interaction_matrix_from_distances(distance_matrix),
min_dist=min_dist,
max_radius=max_radius,
max_dist_to_walk=max_distance_to_walk,
step=step,
modulated_target_interactions=(
modulated_target_interactions if max_distance_to_walk != np.inf else None
),
)
for u, force in enumerate(resulting_forces_vectors):
positions[u] += force
assert not np.any(np.isnan(positions))
if draw_step:
logger.debug(f"Resulting positions = {dict(enumerate(positions))}")
print(f"Current number of dimensions is {positions.shape[-1]}")
distances = distance_matrix[np.triu_indices_from(distance_matrix, k=1)]
print(
f"{min_dist=}, {max_radius=}, "
f"current min dist = {np.min(distances)}, "
f"current max dist = {np.max(distances)}"
)
target_interactions_graph = Qubo.from_matrix(target_interactions).as_graph()
draw_graph_including_actual_weights(
target_interactions_graph=target_interactions_graph,
positions=positions,
draw_weighted_graph=draw_weighted_graph,
)
return positions

Functions:

  • as_2d

    Return the first two coordinates; expects last dim >= 2.

  • draw_set_graph_coords

    Coords are positions in numerical order of the nodes.

as_2d(a: np.ndarray) -> np.ndarray

Return the first two coordinates; expects last dim >= 2.

Source code in qoolqit/embedding/algorithms/blade/drawing.py
def as_2d(a: np.ndarray) -> np.ndarray:
"""Return the first two coordinates; expects last dim >= 2."""
return a[..., :2]
draw_set_graph_coords(graph: nx.Graph, coords: np.ndarray, edge_labels: dict | None = None) -> None

Coords are positions in numerical order of the nodes.

Source code in qoolqit/embedding/algorithms/blade/drawing.py
def draw_set_graph_coords(
graph: nx.Graph, coords: np.ndarray, edge_labels: dict | None = None
) -> None:
"""Coords are positions in numerical order of the nodes."""
nx.set_node_attributes(graph, dict(enumerate(coords)), "pos")
draw_weighted_graph(graph, edge_labels=edge_labels)
plt.show()

Classes:

Functions:

InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass
Section titled “ InteractionEmbedderConfig(method: str = &#39;Nelder-Mead&#39;, maxiter: int = 200000, tol: float = 1e-08) dataclass ”

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)
interaction_embedding(matrix: np.ndarray, method: str, maxiter: int, tol: float) -> DataGraph
Section titled “ interaction_embedding(matrix: np.ndarray, method: str, maxiter: int, tol: float) -&gt; DataGraph ”

Matrix embedding into the interaction term of the Rydberg Analog Model.

Uses scipy.minimize to find the optimal set of node coordinates such that the matrix of values 1/(r_ij)^6 approximate the off-diagonal terms of the input matrix.

Check the documentation for scipy.minimize for more information about each parameter: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html (external)

Parameters:

  • matrix (ndarray) –

    the matrix to embed.

  • method (str) –

    the method used by scipy.minimize.

  • maxiter (int) –

    maximum number of iterations.

  • tol (float) –

    tolerance for termination.

Source code in qoolqit/embedding/algorithms/interaction_embedding.py
def interaction_embedding(matrix: np.ndarray, method: str, maxiter: int, tol: float) -> DataGraph:
"""Matrix embedding into the interaction term of the Rydberg Analog Model.
Uses scipy.minimize to find the optimal set of node coordinates such that the
matrix of values 1/(r_ij)^6 approximate the off-diagonal terms of the input matrix.
Check the documentation for scipy.minimize for more information about each parameter:
https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
Arguments:
matrix: the matrix to embed.
method: the method used by scipy.minimize.
maxiter: maximum number of iterations.
tol: tolerance for termination.
"""
def cost_function(new_coords: np.ndarray, matrix: np.ndarray) -> np.floating[Any]:
"""Cost function."""
new_coords = np.reshape(new_coords, (len(matrix), 2))
# Cost based on minimizing the distance between the matrix and the interaction 1/r^6
new_matrix = squareform(1.0 / (pdist(new_coords) ** 6))
return np.linalg.norm(new_matrix - matrix)
np.random.seed(0)
# Initial guess for the coordinates
x0 = np.random.random(len(matrix) * 2)
res = minimize(
cost_function,
x0,
args=(matrix,),
method=method,
tol=tol,
options={"maxiter": maxiter},
)
coords = np.reshape(res.x, (len(matrix), 2))
centered_coords = coords - np.mean(coords, axis=0)
graph = DataGraph.from_coordinates(centered_coords.tolist())
return graph

Classes:

Functions:

SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass
Section titled “ SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass ”

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)
spring_layout_embedding(graph: DataGraph, iterations: int, threshold: float, seed: int | None) -> DataGraph
Section titled “ spring_layout_embedding(graph: DataGraph, iterations: int, threshold: float, seed: int | None) -&gt; DataGraph ”

Force-directed embedding, wrapping nx.spring_layout.

Generates a new graph with the same nodes and edges as the original graph, but with node coordinates set to embed edge weights into pairwise distances as wᵢⱼ=1/rᵢⱼ^6.

The positions are generated by nx.spring_layout. Since nx.spring_layout embeds edge weights as wᵢⱼ = (k/rᵢⱼ)^3, we set: - k=1 and scale=None to disable global rescaling of positions. - rescale weights wᵢⱼ -> wᵢⱼ^1/2 to achieve our target embedding wᵢⱼ=1/rᵢⱼ^6.

Check the documentation for nx.spring_layout for more information about each parameter: https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html (external)

Parameters:

  • graph (DataGraph) –

    the graph to embed according to its edge weights.

  • iterations (int) –

    maximum number of iterations to take.

  • threshold (float) –

    Threshold for relative error in node position changes. The iteration stops if force-displacement is below this threshold.

  • seed (int | None) –

    random seed for reproducibility.

Returns:

  • DataGraph ( DataGraph ) –

    graph with the node coordinates set according to the spring-layout embedding.

Source code in qoolqit/embedding/algorithms/spring_layout_embedding.py
def spring_layout_embedding(
graph: DataGraph,
iterations: int,
threshold: float,
seed: int | None,
) -> DataGraph:
"""Force-directed embedding, wrapping `nx.spring_layout`.
Generates a new graph with the same nodes and edges as the original graph, but with
node coordinates set to embed edge weights into pairwise distances as wᵢⱼ=1/rᵢⱼ^6.
The positions are generated by `nx.spring_layout`.
Since `nx.spring_layout` embeds edge weights as wᵢⱼ = (k/rᵢⱼ)^3, we set:
- `k=1` and `scale=None` to disable global rescaling of positions.
- rescale weights wᵢⱼ -> wᵢⱼ^1/2
to achieve our target embedding wᵢⱼ=1/rᵢⱼ^6.
Check the documentation for `nx.spring_layout` for more information about each parameter:
https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html
Args:
graph: the graph to embed according to its edge weights.
iterations: maximum number of iterations to take.
threshold: Threshold for relative error in node position changes.
The iteration stops if force-displacement is below this threshold.
seed: random seed for reproducibility.
Returns:
DataGraph: graph with the node coordinates set according to the spring-layout embedding.
"""
# Create a copy of the input graph to avoid modifying weights in-place
graph_for_layout = graph.copy()
# Modify edge weights to embed into wᵢⱼ=1/rᵢⱼ^6
for u, v, weight in graph.edges.data("weight"):
if weight is not None:
graph_for_layout.edges[u, v]["weight"] = np.sqrt(weight)
# Generate the positions using spring_layout with the modified edge weights
positions = nx.spring_layout(
graph_for_layout, k=1, scale=None, iterations=iterations, threshold=threshold, seed=seed
)
# Create a new graph with the same nodes and edges as the original
# but with node coordinates set to be the positions generated by spring_layout
output_graph: DataGraph = graph.copy()
nx.set_node_attributes(output_graph, values=positions, name="pos")
output_graph.coords = positions
return output_graph

Classes:

  • BaseEmbedder

    Abstract base class for all embedders.

  • EmbedderConfig

    Base abstract dataclass for all embedding algorithm configurations.

BaseEmbedder(algorithm: Callable, config: ConfigType)
Section titled “ BaseEmbedder(algorithm: Callable, config: ConfigType) ”

Abstract base class for all embedders.

An embedder is a function that maps a InDataType to an OutDataType through an embedding algorithm. Parameters of the embedding algorithm can be customized through the EmbedderConfig.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

  • validate_input

    Checks if the given data is compatible with the embedder.

  • validate_output

    Checks if the resulting output is expected by the embedder.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result
validate_input(data: InDataType) -> None abstractmethod
Section titled “ validate_input(data: InDataType) -&gt; None abstractmethod ”

Checks if the given data is compatible with the embedder.

Each embedder should write its own data validator. If the data is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • data (InDataType) –

    the data to validate.

Raises:

  • TypeError

    if the data is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_input(self, data: InDataType) -> None:
"""Checks if the given data is compatible with the embedder.
Each embedder should write its own data validator. If the data
is not of the supported type or in the specific supported format
for that embedder, an error should be raised.
Arguments:
data: the data to validate.
Raises:
TypeError: if the data is not of the supported type.
SomeError: some other error if other constraints are not met.
"""
...
validate_output(result: OutDataType) -> None abstractmethod
Section titled “ validate_output(result: OutDataType) -&gt; None abstractmethod ”

Checks if the resulting output is expected by the embedder.

Each embedder should write its own output validator. If the result is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • result (OutDataType) –

    the output to validate.

Raises:

  • TypeError

    if the output is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_output(self, result: OutDataType) -> None:
"""Checks if the resulting output is expected by the embedder.
Each embedder should write its own output validator. If the result
is not of the supported type or in the specific supported format
for that embedder, an error should be raised.
Arguments:
result: the output to validate.
Raises:
TypeError: if the output is not of the supported type.
SomeError: some other error if other constraints are not met.
"""
...

Base abstract dataclass for all embedding algorithm configurations.

Subclasses define parameters specific to their algorithms. Each config should define fields that directly translate to arguments in the respective embedding function it configures.

Methods:

  • dict

    Returns the dataclass as a dictionary.

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
"""Returns the dataclass as a dictionary."""
return asdict(self)

Classes:

GraphToGraphEmbedder(algorithm: Callable, config: ConfigType)
Section titled “ GraphToGraphEmbedder(algorithm: Callable, config: ConfigType) ”

A family of embedders that map a graph to a graph.

Focused on unit-disk graph embedding, where the goal is to find a set of coordinates for a graph that has no coordinates, such that the final unit-disk edges matches the set of edges in the original graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result
SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig())
Section titled “ SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig()) ”

A graph to graph embedder using the spring layout algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/graph_embedder.py
def __init__(self, config: SpringLayoutConfig = SpringLayoutConfig()) -> None:
"""Inits SpringLayoutEmbedder."""
super().__init__(spring_layout_embedding, config=config)

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

Classes:

  • Blade

    A matrix to graph embedder using the BLaDE algorithm.

  • InteractionEmbedder

    A matrix to graph embedder using the interaction embedding algorithm.

  • MatrixToGraphEmbedder

    A family of embedders that map a matrix to a graph.

Blade(config: BladeConfig = BladeConfig())
Section titled “ Blade(config: BladeConfig = BladeConfig()) ”

A matrix to graph embedder using the BLaDE algorithm.

Parameters:

(BladeConfig, default:BladeConfig()) –

configuration object for the BLaDE algorithm.

Methods:

  • embed

    Return a DataGraph with coordinates that embeds the input matrix.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self, config: BladeConfig = BladeConfig()) -> None:
"""Inits Blade.
Args:
config (BladeConfig): configuration object for the BLaDE algorithm.
"""
super().__init__(blade, config=config)

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Return a DataGraph with coordinates that embeds the input matrix.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (ndarray) –

    the matrix to embed into a DataGraph with coordinates.

Source code in qoolqit/embedding/matrix_embedder.py
def embed(self, data: np.ndarray) -> DataGraph:
"""Return a DataGraph with coordinates that embeds the input matrix.
Validates the input, runs the embedding algorithm, and validates the output.
Args:
data (np.ndarray): the matrix to embed into a DataGraph with coordinates.
"""
self.validate_input(data)
positions = self.algorithm(data, **self.config.dict())
graph = DataGraph.from_coordinates(positions.tolist())
return graph

A matrix to graph embedder using the interaction embedding algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self) -> None:
super().__init__(interaction_embedding, InteractionEmbedderConfig())

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result
MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType)
Section titled “ MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType) ”

A family of embedders that map a matrix to a graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

(Callable) –

a callable to the algorithm function.

(ConfigType) –

a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
"""Default initializer for all embedders, taking an algorithm and a config.
An algorithm should be a standalone function that takes a piece of data of an
InDataType and maps it to an OutDataType. Any extra configuration parameters
taken as input by the algorithm function should be defined in the config dataclass,
inheriting from EmbedderConfig.
Arguments:
algorithm: a callable to the algorithm function.
config: a config dataclass holding parameter values for the algorithm.
"""
algo_signature = inspect.signature(algorithm)
if not isinstance(config, EmbedderConfig):
raise TypeError(
"The config must be an instance of a dataclass inheriting from EmbedderConfig."
)
if not set(config.dict().keys()) <= set(algo_signature.parameters):
raise KeyError(
f"Config {config.__class__.__name__} is not compatible with the "
+ f"algorithm {algorithm.__name__}, as not all configuration fields "
+ "correspond to keyword arguments in the algorithm function."
)
self._algorithm = algorithm
self._config = config

Returns the callable to the embedding algorithm.

Returns the config for the embedding algorithm.

Prints info about the embedding algorithm.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
"""Validates the input, runs the embedding algorithm, and validates the output.
Arguments:
data: the data to embed.
"""
self.validate_input(data)
result: OutDataType = self.algorithm(data, **self.config.dict())
self.validate_output(result)
return result

Classes:

  • CompilationError

    An error raised when attempting to compile a program into a Pulser Sequence.

An error raised when attempting to compile a program into a Pulser Sequence.

QoolQit module to execute quantum programs on QPUs or local/remote emulators.

Modules:

Classes:

  • LocalEmulator

    Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

  • QPU

    Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

  • RemoteEmulator

    Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

  • SequenceCompiler

    Compiles a QoolQit Register and Drive to a Device.

LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Section titled “ LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None) ”

Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and local emulator backends.

Parameters:

(type, default:QutipBackendV2) –

backend type. Must be a subtype of pulser.backend.EmulatorBackend.

(EmulationConfig, default:None) –

optional configuration object emulators.

(int, default:None) –

number of bitstring samples to collect from the final quantum state.

Examples:

from qoolqit.execution import LocalEmulator, BackendType
backend = LocalEmulator(backend_type=BackendType.QutipBackendV2)
result = backend.run(program)

Methods:

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
backend_type: type[EmulatorBackend] = QutipBackendV2,
emulation_config: EmulationConfig | None = None,
num_shots: int | None = None,
) -> None:
"""Instantiates a LocalEmulator."""
super().__init__(num_shots=num_shots)
if not issubclass(backend_type, EmulatorBackend):
raise TypeError(
"Error in `LocalEmulator`: `backend_type` must be a EmulatorBackend type."
)
self._backend_type = backend_type
self._emulation_config = self.validate_emulation_config(emulation_config)

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
"""Return a unique emulation config for all emulators.
Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
"""
return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Run a compiled QuantumProgram and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Run a compiled QuantumProgram and return the results."""
self._backend = self._backend_type(program.compiled_sequence, config=self._emulation_config)
results = self._backend.run()
res_seq = (results,) if isinstance(results, Results) else tuple(results)
return res_seq
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig
Section titled “ validate_emulation_config(emulation_config: EmulationConfig | None) -&gt; EmulationConfig ”

Returns a valid config for emulator backends, if needed.

Parameters:

(EmulationConfig | None) –

optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
"""Returns a valid config for emulator backends, if needed.
Args:
emulation_config: optional base configuration class for all emulators backends.
If no config is provided to an emulator backend, the backend default will used.
"""
if emulation_config is None:
emulation_config = self.default_emulation_config()
else:
has_bitstrings = any(
isinstance(obs, BitStrings) for obs in emulation_config.observables
)
if not has_bitstrings:
updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
emulation_config = emulation_config.with_changes(observables=updated_obs)
if self._num_shots is not None:
emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)
return emulation_config

QPU(*, connection: RemoteConnection, num_shots: int | None = None)

Section titled “ QPU(*, connection: RemoteConnection, num_shots: int | None = None) ”

Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

This class provides the primary interface for running quantum programs on actual QPU hardware. It requires authenticated credentials through a connection object to submit and execute programs on remote quantum processors.

Parameters:

(RemoteConnection) –

Authenticated connection to the remote QPU backend.

(int | None, default:None) –

Number of bitstring samples to collect from the final quantum state.

Examples:

Using Pasqal Cloud:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import QPU
connection = PasqalCloud(
username="your_username",
password="your_password",
project_id="your_project_id"
)
backend = QPU(connection=connection)
remote_results = backend.submit(program)

Using Atos MyQML:

from pulser_myqlm import PulserQLMConnection
from qoolqit.execution import QPU
connection = PulserQLMConnection()
backend = QPU(connection=connection)
results = backend.run(program)
Note

Methods:

  • run

    Execute a compiled quantum program on the QPU and return the results.

  • submit

    Submit a compiled quantum program to the QPU and return a result handler.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
connection: RemoteConnection,
num_shots: int | None = None,
) -> None:
"""Instantiates a QPU backend."""
self._backend_type = QPUBackend
self._connection = self.validate_connection(connection)
if num_shots is None:
raise ValueError(
"""Number of shots must be provided to use the QPU backend.
Please specify `num_shots` when creating the QPU instance.
For example: QPU(connection=..., num_shots=100)""",
)
self._config = BackendConfig(default_num_shots=num_shots)
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Execute a compiled quantum program on the QPU and return the results.

This method submits the program and waits for completion before returning the final results.

Parameters:

(QuantumProgram) –

The compiled quantum program to execute on the QPU.

Returns:

  • Sequence[Results]

    The execution results from the QPU.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Execute a compiled quantum program on the QPU and return the results.
This method submits the program and waits for completion before returning
the final results.
Args:
program: The compiled quantum program to execute on the QPU.
Returns:
The execution results from the QPU.
"""
remote_results = self.submit(program, wait=True)
res_seq: Sequence[Results] = remote_results.results
return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults
Section titled “ submit(program: QuantumProgram, wait: bool = False) -&gt; RemoteResults ”

Submit a compiled quantum program to the QPU and return a result handler.

Parameters:

(QuantumProgram) –

The compiled quantum program to execute on the QPU.

(bool, default:False) –

Whether to wait for the QPU to complete execution before returning.

Returns:

  • RemoteResults

    A remote result handler for monitoring job status and retrieving results.

Note
Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
"""Submit a compiled quantum program to the QPU and return a result handler.
Args:
program: The compiled quantum program to execute on the QPU.
wait: Whether to wait for the QPU to complete execution before returning.
Returns:
A remote result handler for monitoring job status and retrieving results.
Note:
The returned RemoteResults object provides:
- Job status monitoring via `get_batch_status()`
- Result retrieval via the `results` property (when job is complete)
"""
self._backend = self._backend_type(
program.compiled_sequence, connection=self._connection, config=self._config
)
remote_results = self._backend.run(wait=wait)
return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod
Section titled “ validate_connection(connection: RemoteConnection) -&gt; RemoteConnection staticmethod ”

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
"""Validate the required connection to instantiate a RemoteBackend.
Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
to send jobs. Validation also happens inside the backend. Early validation just makes the
error easier to understand.
"""
if not isinstance(connection, RemoteConnection):
raise TypeError(f"""Error in `PulserRemoteBackend`:
`connection` must be of type {RemoteConnection}.""")
return connection

RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Section titled “ RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None) ”

Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and remote emulator backends. The behavior is similar to LocalEmulator, but here, requires credentials through a connection to submit/run a program. To get your credentials and to create a connection object, please refer to the Pasqal Cloud interface documentation (external).

Parameters:

(type, default:EmuFreeBackendV2) –

backend type. Must be a subtype of pulser_pasqal.backends.RemoteEmulatorBackend.

(RemoteConnection) –

connection to execute the program on remote backends.

(EmulationConfig, default:None) –

optional configuration object emulators.

(int, default:None) –

number of bitstring samples to collect from the final quantum state.

Examples:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import RemoteEmulator, BackendType
connection = PasqalCloud(username=..., password=..., project_id=...)
backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)
then
remote_results = backend.submit(program)
or
results = backend.run(program)

Methods:

  • default_emulation_config

    Return a unique emulation config for all emulators.

  • run

    Run a compiled QuantumProgram remotely and return the results.

  • submit

    Submit a compiled QuantumProgram and return a remote handler of the results.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

  • validate_emulation_config

    Returns a valid config for emulator backends, if needed.

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2,
connection: RemoteConnection,
emulation_config: EmulationConfig | None = None,
num_shots: int | None = None,
) -> None:
"""Instantiates a RemoteEmulator."""
super().__init__(num_shots=num_shots)
if not issubclass(backend_type, RemoteEmulatorBackend):
raise TypeError(
"Error in `RemoteEmulator`: `backend_type` must be a RemoteEmulatorBackend type."
)
self._backend_type = backend_type
self._connection = self.validate_connection(connection)
self._emulation_config = self.validate_emulation_config(emulation_config)

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
"""Return a unique emulation config for all emulators.
Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
"""
return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Run a compiled QuantumProgram remotely and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Run a compiled QuantumProgram remotely and return the results."""
remote_results = self.submit(program, wait=True)
res_seq: Sequence[Results] = remote_results.results
return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults
Section titled “ submit(program: QuantumProgram, wait: bool = False) -&gt; RemoteResults ”

Submit a compiled QuantumProgram and return a remote handler of the results.

The returned handler RemoteResults can be used to: - query the job status with remote_results.get_batch_status() - when DONE, retrieve results with remote_results.results

Parameters:

(QuantumProgram) –

the compiled quantum program to run.

(bool, default:False) –

Wait for remote backend to complete the job.

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
"""Submit a compiled QuantumProgram and return a remote handler of the results.
The returned handler `RemoteResults` can be used to:
- query the job status with `remote_results.get_batch_status()`
- when DONE, retrieve results with `remote_results.results`
Args:
program (QuantumProgram): the compiled quantum program to run.
wait (bool): Wait for remote backend to complete the job.
"""
# Instantiate backend
self._backend = self._backend_type(
program.compiled_sequence,
connection=self._connection,
config=self._emulation_config,
)
remote_results = self._backend.run(wait=wait)
return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod
Section titled “ validate_connection(connection: RemoteConnection) -&gt; RemoteConnection staticmethod ”

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
"""Validate the required connection to instantiate a RemoteBackend.
Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
to send jobs. Validation also happens inside the backend. Early validation just makes the
error easier to understand.
"""
if not isinstance(connection, RemoteConnection):
raise TypeError(f"""Error in `PulserRemoteBackend`:
`connection` must be of type {RemoteConnection}.""")
return connection
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig
Section titled “ validate_emulation_config(emulation_config: EmulationConfig | None) -&gt; EmulationConfig ”

Returns a valid config for emulator backends, if needed.

Parameters:

(EmulationConfig | None) –

optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
"""Returns a valid config for emulator backends, if needed.
Args:
emulation_config: optional base configuration class for all emulators backends.
If no config is provided to an emulator backend, the backend default will used.
"""
if emulation_config is None:
emulation_config = self.default_emulation_config()
else:
has_bitstrings = any(
isinstance(obs, BitStrings) for obs in emulation_config.observables
)
if not has_bitstrings:
updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
emulation_config = emulation_config.with_changes(observables=updated_obs)
if self._num_shots is not None:
emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)
return emulation_config

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) –

the QoolQit Register.

(Drive) –

the QoolQit Drive.

(Device) –

the QoolQit Device.

(CompilerProfile) –

the CompilerProfile to use.

(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

Classes:

LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)
Section titled “ LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None) ”

Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and local emulator backends.

Parameters:

(type, default:QutipBackendV2) –

backend type. Must be a subtype of pulser.backend.EmulatorBackend.

(EmulationConfig, default:None) –

optional configuration object emulators.

(int, default:None) –

number of bitstring samples to collect from the final quantum state.

Examples:

from qoolqit.execution import LocalEmulator, BackendType
backend = LocalEmulator(backend_type=BackendType.QutipBackendV2)
result = backend.run(program)

Methods:

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
backend_type: type[EmulatorBackend] = QutipBackendV2,
emulation_config: EmulationConfig | None = None,
num_shots: int | None = None,
) -> None:
"""Instantiates a LocalEmulator."""
super().__init__(num_shots=num_shots)
if not issubclass(backend_type, EmulatorBackend):
raise TypeError(
"Error in `LocalEmulator`: `backend_type` must be a EmulatorBackend type."
)
self._backend_type = backend_type
self._emulation_config = self.validate_emulation_config(emulation_config)

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
"""Return a unique emulation config for all emulators.
Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
"""
return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Run a compiled QuantumProgram and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Run a compiled QuantumProgram and return the results."""
self._backend = self._backend_type(program.compiled_sequence, config=self._emulation_config)
results = self._backend.run()
res_seq = (results,) if isinstance(results, Results) else tuple(results)
return res_seq
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig
Section titled “ validate_emulation_config(emulation_config: EmulationConfig | None) -&gt; EmulationConfig ”

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
"""Returns a valid config for emulator backends, if needed.
Args:
emulation_config: optional base configuration class for all emulators backends.
If no config is provided to an emulator backend, the backend default will used.
"""
if emulation_config is None:
emulation_config = self.default_emulation_config()
else:
has_bitstrings = any(
isinstance(obs, BitStrings) for obs in emulation_config.observables
)
if not has_bitstrings:
updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
emulation_config = emulation_config.with_changes(observables=updated_obs)
if self._num_shots is not None:
emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)
return emulation_config
PulserEmulatorBackend(num_shots: int | None = None)
Section titled “ PulserEmulatorBackend(num_shots: int | None = None) ”

Base Emulator class.

Parameters:

(int | None, default:None) –

run the program num_shots times to collect bitstrings statistics. On QPU this represents the actual number of runs of the program. On emulators, the bitstring are sampled from the quantum state num_shots times.

Methods:

Source code in qoolqit/execution/backends.py
def __init__(self, num_shots: int | None = None) -> None:
self._num_shots = num_shots

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
"""Return a unique emulation config for all emulators.
Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
"""
return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig
Section titled “ validate_emulation_config(emulation_config: EmulationConfig | None) -&gt; EmulationConfig ”

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
"""Returns a valid config for emulator backends, if needed.
Args:
emulation_config: optional base configuration class for all emulators backends.
If no config is provided to an emulator backend, the backend default will used.
"""
if emulation_config is None:
emulation_config = self.default_emulation_config()
else:
has_bitstrings = any(
isinstance(obs, BitStrings) for obs in emulation_config.observables
)
if not has_bitstrings:
updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
emulation_config = emulation_config.with_changes(observables=updated_obs)
if self._num_shots is not None:
emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)
return emulation_config

Methods:

validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod
Section titled “ validate_connection(connection: RemoteConnection) -&gt; RemoteConnection staticmethod ”

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
"""Validate the required connection to instantiate a RemoteBackend.
Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
to send jobs. Validation also happens inside the backend. Early validation just makes the
error easier to understand.
"""
if not isinstance(connection, RemoteConnection):
raise TypeError(f"""Error in `PulserRemoteBackend`:
`connection` must be of type {RemoteConnection}.""")
return connection
QPU(*, connection: RemoteConnection, num_shots: int | None = None)
Section titled “ QPU(*, connection: RemoteConnection, num_shots: int | None = None) ”

Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

This class provides the primary interface for running quantum programs on actual QPU hardware. It requires authenticated credentials through a connection object to submit and execute programs on remote quantum processors.

Parameters:

(RemoteConnection) –

Authenticated connection to the remote QPU backend.

(int | None, default:None) –

Number of bitstring samples to collect from the final quantum state.

Examples:

Using Pasqal Cloud:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import QPU
connection = PasqalCloud(
username="your_username",
password="your_password",
project_id="your_project_id"
)
backend = QPU(connection=connection)
remote_results = backend.submit(program)

Using Atos MyQML:

from pulser_myqlm import PulserQLMConnection
from qoolqit.execution import QPU
connection = PulserQLMConnection()
backend = QPU(connection=connection)
results = backend.run(program)
Note

Methods:

  • run

    Execute a compiled quantum program on the QPU and return the results.

  • submit

    Submit a compiled quantum program to the QPU and return a result handler.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
connection: RemoteConnection,
num_shots: int | None = None,
) -> None:
"""Instantiates a QPU backend."""
self._backend_type = QPUBackend
self._connection = self.validate_connection(connection)
if num_shots is None:
raise ValueError(
"""Number of shots must be provided to use the QPU backend.
Please specify `num_shots` when creating the QPU instance.
For example: QPU(connection=..., num_shots=100)""",
)
self._config = BackendConfig(default_num_shots=num_shots)
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Execute a compiled quantum program on the QPU and return the results.

This method submits the program and waits for completion before returning the final results.

Parameters:

  • program (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

Returns:

  • Sequence[Results]

    The execution results from the QPU.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Execute a compiled quantum program on the QPU and return the results.
This method submits the program and waits for completion before returning
the final results.
Args:
program: The compiled quantum program to execute on the QPU.
Returns:
The execution results from the QPU.
"""
remote_results = self.submit(program, wait=True)
res_seq: Sequence[Results] = remote_results.results
return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults
Section titled “ submit(program: QuantumProgram, wait: bool = False) -&gt; RemoteResults ”

Submit a compiled quantum program to the QPU and return a result handler.

Parameters:

  • program (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

  • wait (bool, default: False ) –

    Whether to wait for the QPU to complete execution before returning.

Returns:

  • RemoteResults

    A remote result handler for monitoring job status and retrieving results.

Note
Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
"""Submit a compiled quantum program to the QPU and return a result handler.
Args:
program: The compiled quantum program to execute on the QPU.
wait: Whether to wait for the QPU to complete execution before returning.
Returns:
A remote result handler for monitoring job status and retrieving results.
Note:
The returned RemoteResults object provides:
- Job status monitoring via `get_batch_status()`
- Result retrieval via the `results` property (when job is complete)
"""
self._backend = self._backend_type(
program.compiled_sequence, connection=self._connection, config=self._config
)
remote_results = self._backend.run(wait=wait)
return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod
Section titled “ validate_connection(connection: RemoteConnection) -&gt; RemoteConnection staticmethod ”

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
"""Validate the required connection to instantiate a RemoteBackend.
Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
to send jobs. Validation also happens inside the backend. Early validation just makes the
error easier to understand.
"""
if not isinstance(connection, RemoteConnection):
raise TypeError(f"""Error in `PulserRemoteBackend`:
`connection` must be of type {RemoteConnection}.""")
return connection
RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)
Section titled “ RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None) ”

Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and remote emulator backends. The behavior is similar to LocalEmulator, but here, requires credentials through a connection to submit/run a program. To get your credentials and to create a connection object, please refer to the Pasqal Cloud interface documentation (external).

Parameters:

(type, default:EmuFreeBackendV2) –

backend type. Must be a subtype of pulser_pasqal.backends.RemoteEmulatorBackend.

(RemoteConnection) –

connection to execute the program on remote backends.

(EmulationConfig, default:None) –

optional configuration object emulators.

(int, default:None) –

number of bitstring samples to collect from the final quantum state.

Examples:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import RemoteEmulator, BackendType
connection = PasqalCloud(username=..., password=..., project_id=...)
backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)
then
remote_results = backend.submit(program)
or
results = backend.run(program)

Methods:

  • default_emulation_config

    Return a unique emulation config for all emulators.

  • run

    Run a compiled QuantumProgram remotely and return the results.

  • submit

    Submit a compiled QuantumProgram and return a remote handler of the results.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

  • validate_emulation_config

    Returns a valid config for emulator backends, if needed.

Source code in qoolqit/execution/backends.py
def __init__(
self,
*,
backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2,
connection: RemoteConnection,
emulation_config: EmulationConfig | None = None,
num_shots: int | None = None,
) -> None:
"""Instantiates a RemoteEmulator."""
super().__init__(num_shots=num_shots)
if not issubclass(backend_type, RemoteEmulatorBackend):
raise TypeError(
"Error in `RemoteEmulator`: `backend_type` must be a RemoteEmulatorBackend type."
)
self._backend_type = backend_type
self._connection = self.validate_connection(connection)
self._emulation_config = self.validate_emulation_config(emulation_config)

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
"""Return a unique emulation config for all emulators.
Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
"""
return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]
Section titled “ run(program: QuantumProgram) -&gt; Sequence[Results] ”

Run a compiled QuantumProgram remotely and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
"""Run a compiled QuantumProgram remotely and return the results."""
remote_results = self.submit(program, wait=True)
res_seq: Sequence[Results] = remote_results.results
return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults
Section titled “ submit(program: QuantumProgram, wait: bool = False) -&gt; RemoteResults ”

Submit a compiled QuantumProgram and return a remote handler of the results.

The returned handler RemoteResults can be used to: - query the job status with remote_results.get_batch_status() - when DONE, retrieve results with remote_results.results

Parameters:

  • program (QuantumProgram) –

    the compiled quantum program to run.

  • wait (bool, default: False ) –

    Wait for remote backend to complete the job.

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
"""Submit a compiled QuantumProgram and return a remote handler of the results.
The returned handler `RemoteResults` can be used to:
- query the job status with `remote_results.get_batch_status()`
- when DONE, retrieve results with `remote_results.results`
Args:
program (QuantumProgram): the compiled quantum program to run.
wait (bool): Wait for remote backend to complete the job.
"""
# Instantiate backend
self._backend = self._backend_type(
program.compiled_sequence,
connection=self._connection,
config=self._emulation_config,
)
remote_results = self._backend.run(wait=wait)
return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod
Section titled “ validate_connection(connection: RemoteConnection) -&gt; RemoteConnection staticmethod ”

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
"""Validate the required connection to instantiate a RemoteBackend.
Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
to send jobs. Validation also happens inside the backend. Early validation just makes the
error easier to understand.
"""
if not isinstance(connection, RemoteConnection):
raise TypeError(f"""Error in `PulserRemoteBackend`:
`connection` must be of type {RemoteConnection}.""")
return connection
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig
Section titled “ validate_emulation_config(emulation_config: EmulationConfig | None) -&gt; EmulationConfig ”

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
"""Returns a valid config for emulator backends, if needed.
Args:
emulation_config: optional base configuration class for all emulators backends.
If no config is provided to an emulator backend, the backend default will used.
"""
if emulation_config is None:
emulation_config = self.default_emulation_config()
else:
has_bitstrings = any(
isinstance(obs, BitStrings) for obs in emulation_config.observables
)
if not has_bitstrings:
updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
emulation_config = emulation_config.with_changes(observables=updated_obs)
if self._num_shots is not None:
emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)
return emulation_config

Classes:

Functions:

WaveformConverter(device: Device, time: float, energy: float)
Section titled “ WaveformConverter(device: Device, time: float, energy: float) ”

Convert a QoolQit waveform into a equivalent Pulser waveform.

Requires the new time and energy scales set by the compilation. Additionally, requires the clock period of the device to round the duration.

Methods:

  • convert

    Convert a QoolQit waveform into a equivalent Pulser waveform.

Source code in qoolqit/execution/compilation_functions.py
def __init__(self, device: Device, time: float, energy: float) -> None:
self._time = time
self._energy = energy
self._clock_period = device._clock_period
convert(waveform: Waveform) -> ParamObj | PulserWaveform
Section titled “ convert(waveform: Waveform) -&gt; ParamObj | PulserWaveform ”

Convert a QoolQit waveform into a equivalent Pulser waveform.

Source code in qoolqit/execution/compilation_functions.py
def convert(self, waveform: Waveform) -> ParamObj | PulserWaveform:
"""Convert a QoolQit waveform into a equivalent Pulser waveform."""
pulser_duration = self._pulser_duration(waveform)
return waveform._to_pulser(duration=pulser_duration) * self._energy
basic_compilation(register: Register, drive: Drive, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> PulserSequence
Section titled “ basic_compilation(register: Register, drive: Drive, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -&gt; PulserSequence ”

Compiles a QoolQit program to a PulserSequence.

Defines: - program_energy_ratio: the ratio between the maximum amplitude in the drive and the maximum interaction energy. - device_energy_ratio: the ratio between the device's maximum allowed amplitude in the drive and the maximum possible interaction energy.

If program_energy_ratio > device_energy_ratio the program is scaled to match the device's maximum allowed amplitude. Otherwise, the program is scaled to match the device's minimum allowed pairwise distance.

If the device requires a layout, it is automatically generated.

Parameters:

(Register) –

QoolQit Register.

(Drive) –

QoolQit Drive.

(Device) –

QoolQit Device.

(float | None, default:None) –

optionally set the program duration to a fraction of the device's maximum allowed duration.

Returns:

  • PulserSequence ( Sequence ) –

    The compiled program as a pulser.Sequence object.

Source code in qoolqit/execution/compilation_functions.py
def basic_compilation(
register: Register,
drive: Drive,
device: Device,
profile: CompilerProfile = CompilerProfile.MAX_ENERGY,
device_max_duration_ratio: float | None = None,
) -> PulserSequence:
"""Compiles a QoolQit program to a PulserSequence.
Defines:
- program_energy_ratio: the ratio between the maximum amplitude in the drive
and the maximum interaction energy.
- device_energy_ratio: the ratio between the device's maximum allowed amplitude in the drive
and the maximum possible interaction energy.
If program_energy_ratio > device_energy_ratio the program is scaled to match the device's
maximum allowed amplitude.
Otherwise, the program is scaled to match the device's minimum allowed pairwise distance.
If the device requires a layout, it is automatically generated.
Args:
register: QoolQit Register.
drive: QoolQit Drive.
device: QoolQit Device.
device_max_duration_ratio: optionally set the program duration to a fraction
of the device's maximum allowed duration.
Returns:
PulserSequence: The compiled program as a pulser.Sequence object.
"""
if profile == CompilerProfile.WORKING_POINT:
TIME, ENERGY, DISTANCE = device.converter.factors
_validate_program_default_profile(register, drive, device)
elif profile == CompilerProfile.MAX_ENERGY:
# fix compilation strategy according to the program energy ratio Ω_max/J_max
program_energy_ratio = drive.amplitude.max() * register.min_distance() ** 6
device_energy_ratio = device._energy_ratio
if program_energy_ratio > device_energy_ratio:
# map to the maximum amplitude allowed on the device
ENERGY = device._target_amp / drive.amplitude.max()
TIME, ENERGY, DISTANCE = device.converter.factors_from_energy(ENERGY)
else:
# map to the minimum pairwise distance allowed on the device
DISTANCE = device._target_dist / register.min_distance()
TIME, ENERGY, DISTANCE = device.converter.factors_from_distance(DISTANCE)
_validate_program_max_energy_profile(register, drive, device)
else:
raise ValueError(f"Invalid CompilerProfile: {profile}")
# if device_max_duration_ratio is set and the device has max_duration
# set the duration to the fraction of device's maximum duration.
if device_max_duration_ratio and device._max_duration:
TIME = device_max_duration_ratio * device._max_duration / drive.duration
# Build pulser pulse and register
wf_converter = WaveformConverter(device=device, time=TIME, energy=ENERGY)
pulser_amp_wf = wf_converter.convert(drive._amplitude)
pulser_det_wf = wf_converter.convert(drive._detuning)
pulser_pulse = PulserPulse(pulser_amp_wf, pulser_det_wf, drive.phase)
pulser_register = _build_register(register, device, DISTANCE)
# Create sequence
pulser_device = device._device
pulser_sequence = PulserSequence(pulser_register, pulser_device)
pulser_sequence.declare_channel("rydberg", "rydberg_global")
pulser_sequence.add(pulser_pulse, "rydberg")
if len(drive.weighted_detunings) > 0:
# Add detuning map
channels = list(device._device.dmm_channels.keys())
if len(channels) == 0:
raise ValueError(
f"This program specifies {len(drive.weighted_detunings)} detunings but "
"the device doesn't offer any DMM channel to execute them."
)
detuning_adder = _DetuningAdder(wf_converter, pulser_register, pulser_sequence)
# If our device supports reusable channels, we can declare multiple
# DMM channels with the same specs
if pulser_device.reusable_channels:
# Arbitrarily pick the first channel.
dmm_id = channels[0]
for detuning in drive.weighted_detunings:
detuning_adder.add_detuning(dmm_id, detuning)
# Do we have enough channels for our detunings?
elif len(channels) >= len(drive.weighted_detunings):
for dmm_id, detuning in zip(channels, drive.weighted_detunings):
detuning_adder.add_detuning(dmm_id, detuning)
else:
raise ValueError(
f"This program specifies {len(drive.weighted_detunings)} detunings but "
f"the device only offers {len(channels)} DMM channels to execute them."
)
return pulser_sequence

Classes:

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) –

the QoolQit Register.

(Drive) –

the QoolQit Drive.

(Device) –

the QoolQit Device.

(CompilerProfile) –

the CompilerProfile to use.

(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

Graph creation and manipulation in QoolQit.

Modules:

Classes:

  • BaseGraph

    The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

  • DataGraph

    The main graph structure to represent problem data.

Functions:

  • all_node_pairs

    Return all pairs of nodes (u, v) where u < v.

  • distances

    Return a dictionary of edge distances.

  • random_coords

    Generate a random set of node coordinates on a square of side L.

  • random_edge_list

    Generates a random set of k edges linkings items from a set of nodes.

  • scale_coords

    Scale the coordinates by a given value.

  • space_coords

    Spaces the coordinates so the minimum distance is equal to a set spacing.

The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

Defines basic functionalities for graphs within the Rydberg Analog, such as instantiating from a set of node coordinates, directly accessing node distances, and checking if the graph is unit-disk.

Parameters:

(Iterable, default:[]) –

set of edge tuples (i, j)

Methods:

  • 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_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the 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.

  • 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.

  • 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.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/base_graph.py
def __init__(self, edges: Iterable = []) -> None:
"""
Default constructor for the BaseGraph.
Arguments:
edges: set of edge tuples (i, j)
"""
if edges and not isinstance(edges, Iterable):
raise TypeError("Input is not a valid edge list.")
super().__init__()
self.add_edges_from(edges)
self._coords = {i: None for i in self.nodes}
self._reset_dicts()

Return a list of all possible node pairs in the graph.

Return the dictionary of node coordinates.

Check if the graph has coordinates.

Requires all nodes to have coordinates.

Check if the graph has edge weights.

Requires all edges to have a weight.

Check if the graph has edges.

Check if the graph has node weights.

Requires all nodes to have a weight.

Returns the set of edges (u, v) such that (u < v).

distances(edge_list: Iterable | None = None) -> dict
Section titled “ distances(edge_list: Iterable | None = None) -&gt; 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:

(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) -&gt; 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.

(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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of coordinates.

Parameters:

(list | dict) –

list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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_nodes(nodes: Iterable) -> BaseGraph classmethod
Section titled “ from_nodes(nodes: Iterable) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of nodes.

Parameters:

(Iterable) –

set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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

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
@classmethod
def 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

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()}

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
max_distance(connected: bool | None = None) -> float
Section titled “ max_distance(connected: bool | None = None) -&gt; float ”

Returns the maximum distance in the graph.

Parameters:

(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) -&gt; float ”

Returns the minimum distance in the graph.

Parameters:

(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
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) -&gt; None ”

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

(float | None, default:None) –

value to scale by.

(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.")

Reset the set of edges to be equal to the set of unit-disk edges.

Parameters:

(float) –

the radius to use in determining the set of unit-disk edges.

Source code in qoolqit/graphs/base_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.
Arguments:
radius: the radius to use in determining the set of unit-disk edges.
"""
self.remove_edges_from(list(self.edges))
self.add_edges_from(self.ud_edges(radius))

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

(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.")

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.")

The main graph structure to represent problem data.

Parameters:

(Iterable, default:[]) –

set of edge tuples (i, j)

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)

Return a list of all possible node pairs in the graph.

Return the dictionary of node coordinates.

Return the dictionary of edge weights.

Check if the graph has coordinates.

Requires all nodes to have coordinates.

Check if the graph has edge weights.

Requires all edges to have a weight.

Check if the graph has edges.

Check if the graph has node weights.

Requires all nodes to have a weight.

Return the dictionary of node weights.

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)) -&gt; DataGraph classmethod ”

Constructs a circle graph, with the respective coordinates.

Parameters:

(int) –

number of nodes.

(float, default:1.0) –

distance between each node.

(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
@classmethod
def 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) -&gt; 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:

(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) -&gt; 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.

(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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of coordinates.

Parameters:

(list | dict) –

list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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) -&gt; 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
@classmethod
def 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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of nodes.

Parameters:

(Iterable) –

set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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

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
@classmethod
def 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) -&gt; 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 (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.

Parameters:

(Data) –

PyTorch Geometric Data object to convert.

(Iterable[str] | None, default:None) –

extra node attributes to copy (beyond x and pos).

(Iterable[str] | None, default:None) –

extra edge attributes to copy (beyond edge_attr).

(Iterable[str] | None, default:None) –

extra graph-level attributes to copy (beyond y).

(str | None, default:None) –

Data attribute to use as node weights.

(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_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.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of hexagons.

(int) –

Number of columns of hexagons.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Notes
Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of hexagons.

(int) –

Number of columns of hexagons.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

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()}

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) -&gt; DataGraph classmethod ”

Constructs a line graph, with the respective coordinates.

Parameters:

(int) –

number of nodes.

(float, default:1.0) –

distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; float ”

Returns the maximum distance in the graph.

Parameters:

(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) -&gt; float ”

Returns the minimum distance in the graph.

Parameters:

(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) -&gt; 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
@classmethod
def 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) -&gt; 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.

(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
@classmethod
def 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) -&gt; None ”

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

(float | None, default:None) –

value to scale by.

(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.")

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) -&gt; DataGraph classmethod ”

Constructs a square lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of square.

(int) –

Number of columns of square.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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 = &#39;weight&#39;, edge_weights_attr: str = &#39;edge_weight&#39;) -&gt; 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:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

(Iterable[str] | None, default:None) –

extra node attributes to export (beyond x).

(Iterable[str] | None, default:None) –

extra edge attributes to export (beyond edge_attr).

(Iterable[str] | None, default:None) –

extra graph-level attributes to export (beyond y).

(str, default:'weight') –

Data attribute name for node weights. Defaults to "weight".

(str, default:'edge_weight') –

Data attribute name for edge weights. Defaults to "edge_weight".

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is 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) -&gt; DataGraph classmethod ”

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

(int) –

Number of rows of triangles.

(int) –

Number of columns of triangles.

(float, default:1.0) –

The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

(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.")

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.")

Return all pairs of nodes (u, v) where u < v.

Parameters:

(Iterable) –

set of node indices.

Source code in qoolqit/graphs/utils.py
def all_node_pairs(nodes: Iterable) -> set:
"""Return all pairs of nodes (u, v) where u < v.
Arguments:
nodes: set of node indices.
"""
return set(filter(lambda x: x[0] < x[1], product(nodes, nodes)))

distances(coords: dict, edge_list: Iterable) -> dict

Section titled “ distances(coords: dict, edge_list: Iterable) -&gt; dict ”

Return a dictionary of edge distances.

Parameters:

(dict) –

dictionary of node coordinates.

(Iterable) –

edge list to compute the distances for.

Source code in qoolqit/graphs/utils.py
def distances(coords: dict, edge_list: Iterable) -> dict:
"""Return a dictionary of edge distances.
Arguments:
coords: dictionary of node coordinates.
edge_list: edge list to compute the distances for.
"""
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in edge_list}

Generate a random set of node coordinates on a square of side L.

Parameters:

(int) –

number of coordinate pairs to generate.

(float, default:1.0) –

side of the square.

Source code in qoolqit/graphs/utils.py
def random_coords(n: int, L: float = 1.0) -> list:
"""Generate a random set of node coordinates on a square of side L.
Arguments:
n: number of coordinate pairs to generate.
L: side of the square.
"""
x_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
y_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
return [(x, y) for x, y in zip(x_coords, y_coords)]

random_edge_list(nodes: Iterable, k: int) -> list

Section titled “ random_edge_list(nodes: Iterable, k: int) -&gt; list ”

Generates a random set of k edges linkings items from a set of nodes.

Source code in qoolqit/graphs/utils.py
def random_edge_list(nodes: Iterable, k: int) -> list:
"""Generates a random set of k edges linkings items from a set of nodes."""
all_edges = all_node_pairs(nodes)
return random.sample(tuple(all_edges), k=k)

scale_coords(coords: dict, scaling: float) -> dict

Section titled “ scale_coords(coords: dict, scaling: float) -&gt; dict ”

Scale the coordinates by a given value.

Parameters:

(dict) –

dictionary of node coordinates.

(float) –

value to scale by.

Source code in qoolqit/graphs/utils.py
def scale_coords(coords: dict, scaling: float) -> dict:
"""Scale the coordinates by a given value.
Arguments:
coords: dictionary of node coordinates.
scaling: value to scale by.
"""
scaled_coords = {i: (c[0] * scaling, c[1] * scaling) for i, c in coords.items()}
return scaled_coords

space_coords(coords: dict, spacing: float) -> dict

Section titled “ space_coords(coords: dict, spacing: float) -&gt; dict ”

Spaces the coordinates so the minimum distance is equal to a set spacing.

Parameters:

(dict) –

dictionary of node coordinates.

(float) –

value to set as minimum distance.

Source code in qoolqit/graphs/utils.py
def space_coords(coords: dict, spacing: float) -> dict:
"""Spaces the coordinates so the minimum distance is equal to a set spacing.
Arguments:
coords: dictionary of node coordinates.
spacing: value to set as minimum distance.
"""
pairs = all_node_pairs(list(coords.keys()))
min_dist = min(distances(coords, pairs).values())
scale_factor = spacing / min_dist
return scale_coords(coords, scale_factor)

Classes:

  • BaseGraph

    The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

Defines basic functionalities for graphs within the Rydberg Analog, such as instantiating from a set of node coordinates, directly accessing node distances, and checking if the graph is unit-disk.

Parameters:

(Iterable, default:[]) –

set of edge tuples (i, j)

Methods:

  • 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_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the 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.

  • 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.

  • 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.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/base_graph.py
def __init__(self, edges: Iterable = []) -> None:
"""
Default constructor for the BaseGraph.
Arguments:
edges: set of edge tuples (i, j)
"""
if edges and not isinstance(edges, Iterable):
raise TypeError("Input is not a valid edge list.")
super().__init__()
self.add_edges_from(edges)
self._coords = {i: None for i in self.nodes}
self._reset_dicts()

Return a list of all possible node pairs in the graph.

Return the dictionary of node coordinates.

Check if the graph has coordinates.

Requires all nodes to have coordinates.

Check if the graph has edge weights.

Requires all edges to have a weight.

Check if the graph has edges.

Check if the graph has node weights.

Requires all nodes to have a weight.

Returns the set of edges (u, v) such that (u < v).

distances(edge_list: Iterable | None = None) -> dict
Section titled “ distances(edge_list: Iterable | None = None) -&gt; 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 (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) -&gt; None ”

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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_nodes(nodes: Iterable) -> BaseGraph classmethod
Section titled “ from_nodes(nodes: Iterable) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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

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
@classmethod
def 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

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()}

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
max_distance(connected: bool | None = None) -> float
Section titled “ max_distance(connected: bool | None = None) -&gt; float ”

Returns the maximum distance in the graph.

Parameters:

  • 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) -&gt; float ”

Returns the minimum distance in the graph.

Parameters:

  • 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
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) -&gt; None ”

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling (float | None, default: None ) –

    value to scale by.

  • 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.")

Reset the set of edges to be equal to the set of unit-disk edges.

Parameters:

  • radius (float) –

    the radius to use in determining the set of unit-disk edges.

Source code in qoolqit/graphs/base_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.
Arguments:
radius: the radius to use in determining the set of unit-disk edges.
"""
self.remove_edges_from(list(self.edges))
self.add_edges_from(self.ud_edges(radius))

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • 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.")

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.")

Classes:

  • DataGraph

    The main graph structure to represent problem data.

The main graph structure to represent problem data.

Parameters:

(Iterable, default:[]) –

set of edge tuples (i, j)

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)

Return a list of all possible node pairs in the graph.

Return the dictionary of node coordinates.

Return the dictionary of edge weights.

Check if the graph has coordinates.

Requires all nodes to have coordinates.

Check if the graph has edge weights.

Requires all edges to have a weight.

Check if the graph has edges.

Check if the graph has node weights.

Requires all nodes to have a weight.

Return the dictionary of node weights.

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)) -&gt; DataGraph classmethod ”

Constructs a circle graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

  • 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
@classmethod
def 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) -&gt; 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 (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) -&gt; None ”

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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) -&gt; 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:

  • data (ArrayLike) –

    symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; BaseGraph classmethod ”

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def 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

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
@classmethod
def 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) -&gt; 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 (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.

Parameters:

  • data (Data) –

    PyTorch Geometric Data object to convert.

  • node_attrs (Iterable[str] | None, default: None ) –

    extra node attributes to copy (beyond x and pos).

  • edge_attrs (Iterable[str] | None, default: None ) –

    extra edge attributes to copy (beyond edge_attr).

  • graph_attrs (Iterable[str] | None, default: None ) –

    extra graph-level attributes to copy (beyond y).

  • node_weights_attr (str | None, default: None ) –

    Data attribute to use as node weights.

  • 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_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.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Notes
Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; DataGraph classmethod ”

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

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()}

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) -&gt; DataGraph classmethod ”

Constructs a line graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; float ”

Returns the maximum distance in the graph.

Parameters:

  • 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) -&gt; float ”

Returns the minimum distance in the graph.

Parameters:

  • 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) -&gt; DataGraph classmethod ”

Constructs an Erdős–Rényi random graph.

Parameters:

  • n (int) –

    number of nodes.

  • p (float) –

    probability that any two nodes connect.

  • seed (int | None, default: None ) –

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; 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:

  • n (int) –

    number of nodes.

  • radius (float, default: 1.0 ) –

    radius to use for defining the unit-disk edges.

  • L (float | None, default: None ) –

    size of the square on which to sample the node coordinates.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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) -&gt; None ”

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling (float | None, default: None ) –

    value to scale by.

  • 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.")

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) -&gt; DataGraph classmethod ”

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of square.

  • n (int) –

    Number of columns of square.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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 = &#39;weight&#39;, edge_weights_attr: str = &#39;edge_weight&#39;) -&gt; 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:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

  • node_attrs (Iterable[str] | None, default: None ) –

    extra node attributes to export (beyond x).

  • edge_attrs (Iterable[str] | None, default: None ) –

    extra edge attributes to export (beyond edge_attr).

  • graph_attrs (Iterable[str] | None, default: None ) –

    extra graph-level attributes to export (beyond y).

  • node_weights_attr (str, default: 'weight' ) –

    Data attribute name for node weights. Defaults to "weight".

  • 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_geometric is 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) -&gt; DataGraph classmethod ”

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of triangles.

  • n (int) –

    Number of columns of triangles.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def 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

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • 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.")

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.")

Functions:

  • all_node_pairs

    Return all pairs of nodes (u, v) where u < v.

  • distances

    Return a dictionary of edge distances.

  • less_or_equal

    Less or approximately equal.

  • radial_distances

    Return a dictionary of node distances from the origin.

  • random_coords

    Generate a random set of node coordinates on a square of side L.

  • random_edge_list

    Generates a random set of k edges linkings items from a set of nodes.

  • scale_coords

    Scale the coordinates by a given value.

  • space_coords

    Spaces the coordinates so the minimum distance is equal to a set spacing.

Return all pairs of nodes (u, v) where u < v.

Parameters:

(Iterable) –

set of node indices.

Source code in qoolqit/graphs/utils.py
def all_node_pairs(nodes: Iterable) -> set:
"""Return all pairs of nodes (u, v) where u < v.
Arguments:
nodes: set of node indices.
"""
return set(filter(lambda x: x[0] < x[1], product(nodes, nodes)))
distances(coords: dict, edge_list: Iterable) -> dict
Section titled “ distances(coords: dict, edge_list: Iterable) -&gt; dict ”

Return a dictionary of edge distances.

Parameters:

(dict) –

dictionary of node coordinates.

(Iterable) –

edge list to compute the distances for.

Source code in qoolqit/graphs/utils.py
def distances(coords: dict, edge_list: Iterable) -> dict:
"""Return a dictionary of edge distances.
Arguments:
coords: dictionary of node coordinates.
edge_list: edge list to compute the distances for.
"""
return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in edge_list}
less_or_equal(a: float, b: float, rel_tol: float = 0.0, abs_tol: float = ATOL_32) -> bool
Section titled “ less_or_equal(a: float, b: float, rel_tol: float = 0.0, abs_tol: float = ATOL_32) -&gt; bool ”

Less or approximately equal.

Source code in qoolqit/graphs/utils.py
def less_or_equal(a: float, b: float, rel_tol: float = 0.0, abs_tol: float = ATOL_32) -> bool:
"""Less or approximately equal."""
return a < b or isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)

Return a dictionary of node distances from the origin.

Parameters:

(dict) –

dictionary of node coordinates.

Source code in qoolqit/graphs/utils.py
def radial_distances(coords: dict) -> dict:
"""Return a dictionary of node distances from the origin.
Arguments:
coords: dictionary of node coordinates.
"""
return {key: hypot(*node) for key, node in coords.items()}

Generate a random set of node coordinates on a square of side L.

Parameters:

(int) –

number of coordinate pairs to generate.

(float, default:1.0) –

side of the square.

Source code in qoolqit/graphs/utils.py
def random_coords(n: int, L: float = 1.0) -> list:
"""Generate a random set of node coordinates on a square of side L.
Arguments:
n: number of coordinate pairs to generate.
L: side of the square.
"""
x_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
y_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
return [(x, y) for x, y in zip(x_coords, y_coords)]
random_edge_list(nodes: Iterable, k: int) -> list
Section titled “ random_edge_list(nodes: Iterable, k: int) -&gt; list ”

Generates a random set of k edges linkings items from a set of nodes.

Source code in qoolqit/graphs/utils.py
def random_edge_list(nodes: Iterable, k: int) -> list:
"""Generates a random set of k edges linkings items from a set of nodes."""
all_edges = all_node_pairs(nodes)
return random.sample(tuple(all_edges), k=k)
scale_coords(coords: dict, scaling: float) -> dict
Section titled “ scale_coords(coords: dict, scaling: float) -&gt; dict ”

Scale the coordinates by a given value.

Parameters:

(dict) –

dictionary of node coordinates.

(float) –

value to scale by.

Source code in qoolqit/graphs/utils.py
def scale_coords(coords: dict, scaling: float) -> dict:
"""Scale the coordinates by a given value.
Arguments:
coords: dictionary of node coordinates.
scaling: value to scale by.
"""
scaled_coords = {i: (c[0] * scaling, c[1] * scaling) for i, c in coords.items()}
return scaled_coords
space_coords(coords: dict, spacing: float) -> dict
Section titled “ space_coords(coords: dict, spacing: float) -&gt; dict ”

Spaces the coordinates so the minimum distance is equal to a set spacing.

Parameters:

(dict) –

dictionary of node coordinates.

(float) –

value to set as minimum distance.

Source code in qoolqit/graphs/utils.py
def space_coords(coords: dict, spacing: float) -> dict:
"""Spaces the coordinates so the minimum distance is equal to a set spacing.
Arguments:
coords: dictionary of node coordinates.
spacing: value to set as minimum distance.
"""
pairs = all_node_pairs(list(coords.keys()))
min_dist = min(distances(coords, pairs).values())
scale_factor = spacing / min_dist
return scale_coords(coords, scale_factor)

Classes:

  • QuantumProgram

    A program representing a Sequence acting on a Register of qubits.

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) –

the register of qubits, defining their positions.

(Drive) –

the drive acting on qubits, defining amplitude, detuning and phase.

Methods:

  • compile_to

    Compiles the quantum program for execution on a specific device.

Attributes:

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.")
self._drive = drive
self._compiled_sequence: PulserSequence | None = None
for detuning in drive.weighted_detunings:
for key in detuning.weights.keys():
if key not in register.qubits:
raise ValueError(
"In this QuantumProgram, the drive and the register "
f"do not match: qubit {key} appears in the drive but "
"is not defined in the register."
)
compiled_sequence: PulserSequence property
Section titled “ compiled_sequence: PulserSequence property ”

The Pulser sequence compiled to a specific device.

The driving waveforms.

Check if the program has been compiled.

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) -&gt; 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) –

The target device for compilation.

(CompilerProfile, default:MAX_ENERGY) –

The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.

(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."
)
compiler = SequenceCompiler(
self.register, self.drive, device, profile, device_max_duration_ratio
)
self._device = device
self._compiled_sequence = compiler.compile_sequence()

Classes:

  • Register

    The Register in QoolQit, representing a set of qubits with coordinates.

The Register in QoolQit, representing a set of qubits with coordinates.

Parameters:

(dict) –

a dictionary of qubits and respective coordinates {q: (x, y), ...}.

Methods:

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

Number of qubits in the Register.

Returns the dictionary of qubits and respective coordinates.

Returns the qubit keys.

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) -&gt; Figure | None ”

Draw the register.

Parameters:

(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) -&gt; Register classmethod ”

Initializes a Register from a list of coordinates.

Parameters:

(list) –

a list of coordinates [(x, y), ...]

Source code in qoolqit/register.py
@classmethod
def 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) -&gt; Register classmethod ”

Initializes a Register from a graph that has coordinates.

Parameters:

(DataGraph) –

a DataGraph instance.

Source code in qoolqit/register.py
@classmethod
def 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)

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()}

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

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 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)

Modules:

Classes:

  • Blackman

    A Blackman window of a specified duration and area under the curve.

  • Constant

    A constant waveform over a given duration.

  • Delay

    An empty waveform.

  • Interpolated

    A waveform created from interpolation of a set of data points.

  • PiecewiseLinear

    A piecewise linear waveform.

  • Ramp

    A ramp that linearly interpolates between an initial and final value.

  • Sin

    An arbitrary sine over a given duration.

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:

(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)

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.

A constant waveform over a given duration.

Parameters:

(float) –

the total duration.

(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)

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.

Delay(duration: float, *args: float, **kwargs: float | np.ndarray)

Section titled “ Delay(duration: float, *args: float, **kwargs: float | np.ndarray) ”

An empty waveform.

Parameters:

(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)

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.

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 = &#39;PchipInterpolator&#39;, **interpolator_kwargs: Any) ”

A waveform created from interpolation of a set of data points.

Parameters:

(int) –

The waveform duration (in ns).

(ArrayLike) –

Values of the interpolation points. Must be a list of castable to float or a parametrized object.

(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.

(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)

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.

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:

(list | tuple) –

list or tuple of N duration 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)

Returns the duration of the waveform.

Returns the list of durations of each individual waveform.

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.

Returns the list of times when each individual waveform starts.

Returns a list of the individual waveforms.

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

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])

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)

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:

(float) –

the total duration.

(float) –

the initial value at t = 0.

(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)

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.

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:

(float) –

the total duration.

(float, default:1.0) –

the amplitude of the sine wave.

(float, default:1.0) –

the frequency of the sine wave.

(float, default:0.0) –

the phase of the sine wave.

(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)

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.

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)

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)

Classes:

Base class for composite waveforms.

A CompositeWaveform stores a sequence of waveforms occurring one after the other by the order given. When it is evaluated at time t, the corresponding waveform from the sequence is identified depending on the duration of each one, and it is then evaluated for a time t' = t minus the duration of all previous waveforms.

Parameters:

(Waveform, default:()) –

an iterator over waveforms.

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/base_waveforms.py
def __init__(self, *waveforms: Waveform) -> None:
"""Initializes the CompositeWaveform.
Arguments:
waveforms: an iterator over waveforms.
"""
if not all(isinstance(wf, Waveform) for wf in waveforms):
raise TypeError("All arguments must be instances of Waveform.")
if not waveforms:
raise ValueError("At least one Waveform must be provided.")
self._waveforms = []
for wf in waveforms:
if isinstance(wf, CompositeWaveform):
self._waveforms += wf.waveforms
else:
self._waveforms.append(wf)
super().__init__(sum(self.durations))

Returns the duration of the waveform.

Returns the list of durations of each individual waveform.

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.

Returns the list of times when each individual waveform starts.

Returns a list of the individual waveforms.

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

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])

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)
Waveform(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “ Waveform(duration: float, *args: float, **kwargs: float | np.ndarray) ”

Base class for waveforms.

A Waveform is a function of time for t >= 0. Custom waveforms can be defined by inheriting from the base class and overriding the function method corresponding to the function f(t) that returns the value of the waveform evaluated at time t.

A waveform is always a 1D function, so if it includes other parameters, these should be passed and saved at initialization for usage within the function method.

Parameters:

(float) –

the total duration of the waveform.

Methods:

  • function

    Evaluates the waveform function at a given time t.

  • 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/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)

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.

Evaluates the waveform function at a given time t.

Source code in qoolqit/waveforms/base_waveforms.py
@abstractmethod
def function(self, t: float) -> float:
"""Evaluates the waveform function at a given time t."""
...

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)

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)

Functions:

  • round_to_sum

    Round a list of numbers such that their sum is the rounded sum.

round_to_sum(values: list[float]) -> list[int]
Section titled “ round_to_sum(values: list[float]) -&gt; list[int] ”

Round a list of numbers such that their sum is the rounded sum.

Σᵢround(aᵢ) = round(Σᵢaᵢ)

Example
>>> round_to_sum([100.3, 100.3, 100.4])
>>> [100, 100, 101]
Source code in qoolqit/waveforms/utils.py
def round_to_sum(values: list[float]) -> list[int]:
"""Round a list of numbers such that their sum is the rounded sum.
Σᵢround(aᵢ) = round(Σᵢaᵢ)
Example:
```python
>>> round_to_sum([100.3, 100.3, 100.4])
>>> [100, 100, 101]
```
"""
rounded_values = [round(el) for el in values]
reminders = [el - rel for rel, el in zip(rounded_values, values)]
sum_reminders = round(sum(reminders))
p = np.argsort(reminders)
for i in range(abs(sum_reminders)):
if sum_reminders < 0:
rounded_values[p[i]] -= 1
if sum_reminders > 0:
rounded_values[p[-1 - i]] += 1
return rounded_values

Classes:

  • Blackman

    A Blackman window of a specified duration and area under the curve.

  • Constant

    A constant waveform over a given duration.

  • Delay

    An empty waveform.

  • Interpolated

    A waveform created from interpolation of a set of data points.

  • PiecewiseLinear

    A piecewise linear waveform.

  • Ramp

    A ramp that linearly interpolates between an initial and final value.

  • Sin

    An arbitrary sine over a given duration.

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:

(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)

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.

A constant waveform over a given duration.

Parameters:

(float) –

the total duration.

(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)

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.

Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “ Delay(duration: float, *args: float, **kwargs: float | np.ndarray) ”

An empty waveform.

Parameters:

(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)

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.

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 = &#39;PchipInterpolator&#39;, **interpolator_kwargs: Any) ”

A waveform created from interpolation of a set of data points.

Parameters:

(int) –

The waveform duration (in ns).

(ArrayLike) –

Values of the interpolation points. Must be a list of castable to float or a parametrized object.

(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.

(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)

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.

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:

(list | tuple) –

list or tuple of N duration 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)

Returns the duration of the waveform.

Returns the list of durations of each individual waveform.

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.

Returns the list of times when each individual waveform starts.

Returns a list of the individual waveforms.

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

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])

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)
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:

(float) –

the total duration.

(float) –

the initial value at t = 0.

(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)

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.

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:

(float) –

the total duration.

(float, default:1.0) –

the amplitude of the sine wave.

(float, default:1.0) –

the frequency of the sine wave.

(float, default:0.0) –

the phase of the sine wave.

(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)

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.

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)

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)