API reference
qoolqit
Section titled “
qoolqit
”A Python library for algorithm development in the Rydberg Analog Model.
Modules:
-
devices– -
drive– -
embedding–Collection of graph and matrix embedding algorithms.
-
exceptions– -
execution–QoolQit module to execute quantum programs on QPUs or local/remote emulators.
-
graphs–Graph creation and manipulation in QoolQit.
-
program– -
register– -
waveforms–
Classes:
-
AnalogDevice–A realistic device for analog sequence execution.
-
Blackman–A Blackman window of a specified duration and area under the curve.
-
Constant–A constant waveform over a given duration.
-
DataGraph–The main graph structure to represent problem data.
-
Delay–An empty waveform.
-
Device–QoolQit Device wrapper around a Pulser BaseDevice.
-
DigitalAnalogDevice–A device with digital and analog capabilities.
-
Drive–The drive Hamiltonian acting over a duration.
-
Interpolated–A waveform created from interpolation of a set of data points.
-
MockDevice–A virtual device for unconstrained prototyping.
-
PiecewiseLinear–A piecewise linear waveform.
-
QuantumProgram–A program representing a Sequence acting on a Register of qubits.
-
Ramp–A ramp that linearly interpolates between an initial and final value.
-
Register–The Register in QoolQit, representing a set of qubits with coordinates.
-
SequenceCompiler–Compiles a QoolQit Register and Drive to a Device.
-
Sin–An arbitrary sine over a given duration.
Functions:
-
available_default_devices–Show the default available devices in QooQit.
AnalogDevice()
Section titled “
AnalogDevice()
”A realistic device for analog sequence execution.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Blackman(duration: float, area: float)
Section titled “
Blackman(duration: float, area: float)
”A Blackman window of a specified duration and area under the curve.
Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration
See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window (external)
Parameters:
duration
Section titled “ duration
”float)
–The waveform duration.
float)
–The integral of the waveform.
Example
blackman_wf = Blackman(100.0, area=3.14)Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None: """Initializes a new Blackman waveform.""" super().__init__(duration, area=area)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Constant(duration: float, value: float)
Section titled “
Constant(duration: float, value: float)
”A constant waveform over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
value
Section titled “ value
”float)
–the value to take during the duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, value: float,) -> None: super().__init__(duration, value=value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
DataGraph(edges: Iterable = [])
Section titled “
DataGraph(edges: Iterable = [])
”The main graph structure to represent problem data.
Parameters:
edges
Section titled “ edges
”Iterable, default:[])
–set of edge tuples (i, j)
Methods:
-
circle–Constructs a circle graph, with the respective coordinates.
-
distances–Returns a dictionary of distances for a given set of edges.
-
draw–Draw the graph.
-
from_coordinates–Construct a base graph from a set of coordinates.
-
from_matrix–Constructs a graph from a symmetric square matrix.
-
from_nodes–Construct a base graph from a set of nodes.
-
from_nx–Convert a NetworkX Graph object into a QoolQit graph instance.
-
from_pyg–Convert a PyTorch Geometric Data object into a DataGraph instance.
-
heavy_hexagonal–Constructs a heavy-hexagonal lattice graph, with respective coordinates.
-
hexagonal–Constructs a hexagonal lattice graph, with respective coordinates.
-
interactions–Rydberg model interaction 1/r^6 between pair of nodes.
-
is_ud_graph–Check if the graph is unit-disk.
-
line–Constructs a line graph, with the respective coordinates.
-
max_distance–Returns the maximum distance in the graph.
-
min_distance–Returns the minimum distance in the graph.
-
random_er–Constructs an Erdős–Rényi random graph.
-
random_ud–Constructs a random unit-disk graph.
-
rescale_coords–Rescales the node coordinates by a factor.
-
set_ud_edges–Reset the set of edges to be equal to the set of unit-disk edges.
-
square–Constructs a square lattice graph, with respective coordinates.
-
to_pyg–Convert the DataGraph to a PyTorch Geometric Data object.
-
triangular–Constructs a triangular lattice graph, with respective coordinates.
-
ud_edges–Returns the set of edges given by the intersection of circles of a given radius.
-
ud_radius_range–Return the range (R_min, R_max) where the graph is unit-disk.
Attributes:
-
all_node_pairs(set) –Return a list of all possible node pairs in the graph.
-
coords(dict) –Return the dictionary of node coordinates.
-
edge_weights(dict) –Return the dictionary of edge weights.
-
has_coords(bool) –Check if the graph has coordinates.
-
has_edge_weights(bool) –Check if the graph has edge weights.
-
has_edges(bool) –Check if the graph has edges.
-
has_node_weights(bool) –Check if the graph has node weights.
-
node_weights(dict) –Return the dictionary of node weights.
-
sorted_edges(set) –Returns the set of edges (u, v) such that (u < v).
Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None: """ Default constructor for the BaseGraph.
Arguments: edges: set of edge tuples (i, j) """ super().__init__(edges)
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
edge_weights: dict
property
writable
Section titled “
edge_weights: dict
property
writable
”Return the dictionary of edge weights.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
node_weights: dict
property
writable
Section titled “
node_weights: dict
property
writable
”Return the dictionary of node weights.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”Returns the set of edges (u, v) such that (u < v).
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
Section titled “
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
”Constructs a circle graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
center
Section titled “ center
”tuple, default:(0.0, 0.0))
–point (x, y) to set as the center of the graph.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef circle( cls, n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0),) -> DataGraph: """Constructs a circle graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. center: point (x, y) to set as the center of the graph. """
d_theta = (2.0 * np.pi) / n r = spacing / (2.0 * np.sin(np.pi / n)) theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n) coords = [ (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta)) ] edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)] graph = cls.from_coordinates(coords) graph.add_edges_from(edges) graph._reset_dicts() return graph
distances(edge_list: Iterable | None = None) -> dict
Section titled “
distances(edge_list: Iterable | None = None) -> dict
”Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Parameters:
edge_list
Section titled “ edge_list
”Iterable | None, default:None)
–set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
(Axes | None, default:None)
–Axes object to draw on. If None, uses the current Axes.
**kwargs
Section titled “ **kwargs
”Any, default:{})
–keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
coords
Section titled “ coords
”list | dict)
–list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_matrix(data: ArrayLike) -> DataGraph
classmethod
Section titled “
from_matrix(data: ArrayLike) -> DataGraph
classmethod
”Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Parameters:
(ArrayLike)
–symmetric square matrix.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_matrix(cls, data: ArrayLike) -> DataGraph: """Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Arguments: data: symmetric square matrix. """ if data.ndim != 2: raise ValueError("2D Matrix required.") if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32): raise ValueError("Matrix must be symmetric.")
diag = np.diag(data) n_nodes = len(diag) node_weights = {i: diag[i] for i in range(n_nodes)} if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32): node_weights = {i: None for i in range(n_nodes)} else: node_weights = {i: diag[i].item() for i in range(n_nodes)}
data[data <= ATOL_32] = 0.0 non_zero = data.nonzero() i_list = non_zero[0].tolist() j_list = non_zero[1].tolist()
edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j] edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}
graph = cls.from_nodes(range(n_nodes)) graph.add_edges_from(edge_list) graph.node_weights = node_weights graph.edge_weights = edge_weights return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
nodes
Section titled “ nodes
”Iterable)
–set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
Section titled “
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
”Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires torch_geometric. Uses to_networkx internally.
Default attributes copied (if present on data ):
- Node:
x,pos(posis also stored in_coords) - Edge:
edge_attr - Graph:
y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit weights (_node_weights, _edge_weights) are not
populated automatically — use the explicit parameters:
node_weights_attr: real-valued tensor of shape(N,)or(N, 1). Defaults toNone.edge_weights_attr: real-valued tensor of shape(E,)or(E, 1)whereE = edge_index.shape[1](directed count). Defaults toNone.
The weight attribute is also stored as a regular node/edge attribute.
Parameters:
(Data)
–PyTorch Geometric Data object to convert.
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to copy (beyond x and pos).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to copy (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to copy (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str | None, default:None)
–Data attribute to use as node weights.
edge_weights_attr
Section titled “ edge_weights_attr
”str | None, default:None)
–Data attribute to use as edge weights.
Returns:
-
DataGraph–DataGraph with
_coords,_node_weights,_edge_weights -
DataGraph–populated where applicable.
Raises:
-
ImportError–if
torch_geometricis not installed. -
TypeError–if
datais not atorch_geometric.data.Datainstance, or if a weight attribute is not atorch.Tensor. -
AttributeError–if a specified weight attribute is missing.
-
ValueError–if a weight tensor has an incompatible shape or size.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_pyg( cls, data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None,) -> DataGraph: """Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires ``torch_geometric``. Uses ``to_networkx`` internally.
**Default attributes copied (if present on** ``data`` **):**
- Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``) - Edge: ``edge_attr`` - Graph: ``y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit weights** (``_node_weights``, ``_edge_weights``) are not populated automatically — use the explicit parameters:
- ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or ``(N, 1)``. Defaults to ``None``. - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count). Defaults to ``None``.
The weight attribute is also stored as a regular node/edge attribute.
Arguments: data: PyTorch Geometric Data object to convert. node_attrs: extra node attributes to copy (beyond x and pos). edge_attrs: extra edge attributes to copy (beyond edge_attr). graph_attrs: extra graph-level attributes to copy (beyond y). node_weights_attr: Data attribute to use as node weights. edge_weights_attr: Data attribute to use as edge weights.
Returns: DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights`` populated where applicable.
Raises: ImportError: if ``torch_geometric`` is not installed. TypeError: if ``data`` is not a ``torch_geometric.data.Data`` instance, or if a weight attribute is not a ``torch.Tensor``. AttributeError: if a specified weight attribute is missing. ValueError: if a weight tensor has an incompatible shape or size. """ try: from torch_geometric.data import Data from torch_geometric.utils import to_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
if not isinstance(data, Data): raise TypeError("Input must be a torch_geometric.data.Data object.")
# Validate weight attrs early and keep the squeezed tensors node_tensor = ( cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node") if node_weights_attr is not None else None ) edge_tensor = ( cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge") if edge_weights_attr is not None else None )
# Select unique attributes and add default ones only if present in the data node_attrs_set = {k for k in {"x", "pos"} if k in data} if node_attrs is not None: node_attrs_set |= set(node_attrs) if node_weights_attr is not None: node_attrs_set.add(node_weights_attr)
edge_attrs_set = {k for k in {"edge_attr"} if k in data} if edge_attrs is not None: edge_attrs_set |= set(edge_attrs) if edge_weights_attr is not None: edge_attrs_set.add(edge_weights_attr)
graph_attrs_set = {k for k in {"y"} if k in data} if graph_attrs is not None: graph_attrs_set |= set(graph_attrs)
# Convert to NetworkX (undirected, no self-loops) nx_graph = to_networkx( data, node_attrs=list(node_attrs_set), edge_attrs=list(edge_attrs_set), graph_attrs=list(graph_attrs_set), to_undirected=True, remove_self_loops=True, )
# Build the DataGraph: edges carry their data, nodes carry their data graph = cls(nx_graph.edges(data=True)) graph.add_nodes_from(nx_graph.nodes(data=True)) graph.graph = nx_graph.graph
# Re-initialize QoolQit internal dicts for all nodes/edges graph._coords = {n: None for n in graph.nodes} graph._reset_dicts()
# pos → _coords (stored as list [x, y] by to_networkx) for node, node_data in nx_graph.nodes(data=True): if "pos" in node_data: graph._coords[node] = tuple(node_data["pos"]) # type: ignore[assignment]
# node_weights_attr → _node_weights if node_tensor is not None: for i in range(data.num_nodes): graph._node_weights[i] = node_tensor[i].item()
# edge_weights_attr → _edge_weights if edge_tensor is not None: seen: set = set() for idx in range(data.edge_index.shape[1]): u = int(data.edge_index[0, idx].item()) v = int(data.edge_index[1, idx].item()) key = (min(u, v), max(u, v)) if key not in seen: graph._edge_weights[key] = edge_tensor[idx].item() seen.add(key)
return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Notes
Source code in qoolqit/graphs/data_graph.py
@classmethoddef heavy_hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice.
Notes: The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site. """ G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True) pos_unit = nx.get_node_attributes(G_hex, "pos")
G_heavy = nx.Graph() scaling_factor = 2 * spacing
label_map = {} for old_label, (x, y) in pos_unit.items(): # Relabel to an even-integer grid to make space for midpoint nodes new_label = (2 * old_label[0], 2 * old_label[1]) label_map[old_label] = new_label
# Scale positions and add the node to the new graph new_pos = (x * scaling_factor, y * scaling_factor) G_heavy.add_node(new_label, pos=new_pos)
for u_old, v_old in G_hex.edges(): u_new, v_new = label_map[u_old], label_map[v_old]
mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)
pos_u = G_heavy.nodes[u_new]["pos"] pos_v = G_heavy.nodes[v_new]["pos"] mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)
G_heavy.add_node(mid_label, pos=mid_pos) G_heavy.add_edge(u_new, mid_label) G_heavy.add_edge(mid_label, v_new)
final_nodes = sorted(list(G_heavy.nodes()))
final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]
label_to_int = {label: i for i, label in enumerate(final_nodes)}
final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]
graph = cls.from_coordinates(final_coords) graph.add_edges_from(final_edges) graph._reset_dicts() return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.hexagonal_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a line graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef line(cls, n: int, spacing: float = 1.0) -> DataGraph: """Constructs a line graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. """ coords = [(i * spacing, 0.0) for i in range(n)] graph = cls.from_coordinates(coords) edges = [(i, i + 1) for i in range(0, n - 1)] graph.add_edges_from(edges) graph._reset_dicts() return graph
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = min(self.distances(self.all_node_pairs).values()) elif connected: distance = min(self.distances(self.sorted_edges).values()) else: distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
Section titled “
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
”Constructs an Erdős–Rényi random graph.
Parameters:
(int)
–number of nodes.
float)
–probability that any two nodes connect.
int | None, default:None)
–random seed.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph: """Constructs an Erdős–Rényi random graph.
Arguments: n: number of nodes. p: probability that any two nodes connect. seed: random seed. """ base_graph = nx.erdos_renyi_graph(n, p, seed) graph = DataGraph.from_nodes(list(base_graph.nodes)) graph.add_edges_from(base_graph.edges) graph._reset_dicts() return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
Section titled “
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
”Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Parameters:
(int)
–number of nodes.
radius
Section titled “ radius
”float, default:1.0)
–radius to use for defining the unit-disk edges.
float | None, default:None)
–size of the square on which to sample the node coordinates.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_ud( cls, n: int, radius: float = 1.0, L: float | None = None,) -> DataGraph: """Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Arguments: n: number of nodes. radius: radius to use for defining the unit-disk edges. L: size of the square on which to sample the node coordinates. """ if L is None: L = (radius / 2) * ((np.pi * n) ** 0.5) coords = random_coords(n, L) graph = cls.from_coordinates(coords) edges = graph.ud_edges(radius) graph.add_edges_from(edges) graph._reset_dicts() return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
Section titled “
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
scaling
Section titled “ scaling
”float | None, default:None)
–value to scale by.
spacing
Section titled “ spacing
”float | None, default:None)
–value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None: """Reset the set of edges to be equal to the set of unit-disk edges.""" super().set_ud_edges(radius=radius) self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a square lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of square.
int)
–Number of columns of square.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef square( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a square lattice graph, with respective coordinates.
Arguments: m: Number of rows of square. n: Number of columns of square. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.grid_2d_graph(m, n) final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)] G = nx.convert_node_labels_to_integers(G)
graph = DataGraph.from_coordinates(final_coords) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
Section titled “
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
”Convert the DataGraph to a PyTorch Geometric Data object.
Requires torch_geometric. Uses from_networkx internally.
Default attributes exported (if present on the graph):
- Node
"x"→data.x; Edge"edge_attr"→data.edge_attr - Graph
"y"→data.y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit internal dicts exported when populated:
_coords→data.pos(float64, shape(N, 2))_node_weights→data.<node_weights_attr>(float64, shape(N,)). Defaults to"weight"._edge_weights→data.<edge_weights_attr>(float64, shape(2*E,)). Defaults to"edge_weight".
Parameters:
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to export (beyond x).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to export (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to export (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str, default:'weight')
–Data attribute name for node weights.
Defaults to "weight".
edge_weights_attr
Section titled “ edge_weights_attr
”str, default:'edge_weight')
–Data attribute name for edge weights.
Defaults to "edge_weight".
Returns:
-
Data–PyTorch Geometric Data object.
Raises:
-
ImportError–if
torch_geometricis not installed.
Source code in qoolqit/graphs/data_graph.py
def to_pyg( self, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = "weight", edge_weights_attr: str = "edge_weight",) -> torch_geometric.data.Data: """Convert the DataGraph to a PyTorch Geometric Data object.
Requires ``torch_geometric``. Uses ``from_networkx`` internally.
**Default attributes exported (if present on the graph):**
- Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr`` - Graph ``"y"`` → ``data.y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit internal dicts exported when populated:**
- ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``) - ``_node_weights`` → ``data.`` (float64, shape ``(N,)``). Defaults to ``"weight"``. - ``_edge_weights`` → ``data.`` (float64, shape ``(2*E,)``). Defaults to ``"edge_weight"``.
Arguments: node_attrs: extra node attributes to export (beyond x). edge_attrs: extra edge attributes to export (beyond edge_attr). graph_attrs: extra graph-level attributes to export (beyond y). node_weights_attr: Data attribute name for node weights. Defaults to ``"weight"``. edge_weights_attr: Data attribute name for edge weights. Defaults to ``"edge_weight"``.
Returns: PyTorch Geometric Data object.
Raises: ImportError: if ``torch_geometric`` is not installed. """ try: import torch from torch_geometric.utils import from_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
node_attrs_set = set(node_attrs) if node_attrs else set() edge_attrs_set = set(edge_attrs) if edge_attrs else set() graph_attrs_list = list(graph_attrs) if graph_attrs else []
# Add default PyG attributes if present in the graph if any("x" in d for _, d in self.nodes(data=True)): node_attrs_set.add("x") if any("edge_attr" in d for _, _, d in self.edges(data=True)): edge_attrs_set.add("edge_attr") if "y" in self.graph: graph_attrs_list.append("y")
# Build a filtered copy with only the requested attributes filtered_graph = nx.Graph() filtered_graph.add_nodes_from(self.nodes()) filtered_graph.add_edges_from(self.edges())
for node, node_data in self.nodes(data=True): for key, value in node_data.items(): if key in node_attrs_set: filtered_graph.nodes[node][key] = value for u, v, edge_data in self.edges(data=True): for key, value in edge_data.items(): if key in edge_attrs_set: filtered_graph.edges[u, v][key] = value for attr in graph_attrs_list: if attr in self.graph: filtered_graph.graph[attr] = self.graph[attr]
data = from_networkx(filtered_graph)
# Export _coords → pos if self.has_coords: positions = [self._coords[n] for n in sorted(self.nodes())] data.pos = torch.tensor(positions, dtype=torch.float64)
# Export _node_weights → node_weights_attr if self.has_node_weights: weights = [self._node_weights[n] for n in sorted(self.nodes())] setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))
# Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index) if self.has_edge_weights: edge_weights: list[float] = [] for i in range(data.edge_index.shape[1]): u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item()) edge_key = (min(u, v), max(u, v)) edge_weights.append(float(self._edge_weights[edge_key])) # type: ignore[arg-type] setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))
return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a triangular lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of triangles.
int)
–Number of columns of triangles.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef triangular( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a triangular lattice graph, with respective coordinates.
Arguments: m: Number of rows of triangles. n: Number of columns of triangles. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.triangular_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
radius
Section titled “ radius
”float)
–the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
”An empty waveform.
Parameters:
duration
Section titled “ duration
”float)
–the total duration of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def __init__( self, duration: float, *args: float, **kwargs: float | np.ndarray,) -> None: """Initializes the Waveform.
Arguments: duration: the total duration of the waveform. """
if duration <= 0: raise ValueError("Duration needs to be a positive non-zero value.")
if len(args) > 0: raise ValueError( f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments" )
self._duration = duration self._params_dict = kwargs
self._max: float | None = None self._min: float | None = None
for key, value in kwargs.items(): setattr(self, key, value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
Section titled “
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
”QoolQit Device wrapper around a Pulser BaseDevice.
Parameters:
pulser_device
Section titled “ pulser_device
”BaseDevice)
–a BaseDevice to build the QoolQit device from.
default_converter
Section titled “ default_converter
”Optional[UnitConverter], default:None)
–optional unit converter to handle unit conversion.
Examples:
From Pulser device:
qoolqit_device = Device(pulser_device=pulser_device)From remote Pulser device:
from pulser_pasqal import PasqalCloudfrom qoolqit import Device
# Fetch the remote device from the connectionconnection = PasqalCloud()pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Devicefresnel_device = Device(pulser_device=PulserFresnelDevice)From custom Pulser device:
from dataclasses import replacefrom pulser import AnalogDevicefrom qoolqit import Device
# Converting the pulser Device object in a VirtualDevice objectVirtualAnalog = AnalogDevice.to_virtual()# Replacing desired valuesModdedAnalogDevice = replace( VirtualAnalog, max_radial_distance=100, max_sequence_duration=7000 )
# Wrap a Pulser device object into a QoolQit Devicemod_analog_device = Device(pulser_device=ModdedAnalogDevice)Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__( self, pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None,) -> None:
if not isinstance(pulser_device, BaseDevice): raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")
# Store it for all subsequent lookups self._pulser_device: BaseDevice = pulser_device self._name: str = self._pulser_device.name
# Physical constants / channel & limit lookups (assumes 'rydberg_global' channel) self._C6 = self._pulser_device.interaction_coeff self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period # Relevant limits from the underlying device (float or None) self._max_duration = self._pulser_device.max_sequence_duration self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp self._upper_amp = self._max_amp or 4 * math.pi self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning self._min_distance = self._pulser_device.min_atom_distance self._lower_distance = self._min_distance or 5.0 self._max_radial_distance = self._pulser_device.max_radial_distance
# ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6 self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6
# layouts self._requires_layout = self._pulser_device.requires_layout
if default_converter is not None: # Snapshot the caller-provided factors so reset() reproduces them exactly. t0, e0, d0 = default_converter.factors self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter( self._C6, t0, e0, d0 ) else: self._default_factory = lambda: UnitConverter.from_distance( self._C6, self._lower_distance )
self.reset_converter()
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
DigitalAnalogDevice()
Section titled “
DigitalAnalogDevice()
”A device with digital and analog capabilities.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Drive(*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:
amplitude
Section titled “ amplitude
”Waveform | None, default:None)
–waveform representing Ω(t) in the drive Hamiltonian.
detuning
Section titled “ detuning
”Waveform | None, default:None)
–waveform representing δ(t) in the drive Hamiltonian.
phase
Section titled “ phase
”float, default:0.0)
–phase value ɸ for the amplitude term.
weighted_detunings
Section titled “ weighted_detunings
”list[WeightedDetuning] | None, default:None)
–additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.
Attributes:
-
amplitude(Waveform) –The amplitude waveform in the drive.
-
detuning(Waveform) –The detuning waveform in the drive.
-
phase(float) –The phase value in the drive.
-
weighted_detunings(Sequence[WeightedDetuning]) –Detunings applied to individual qubits.
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 []
amplitude: Waveform
property
Section titled “
amplitude: Waveform
property
”The amplitude waveform in the drive.
detuning: Waveform
property
Section titled “
detuning: Waveform
property
”The detuning waveform in the drive.
phase: float
property
Section titled “
phase: float
property
”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 = 'PchipInterpolator', **interpolator_kwargs: Any)
”A waveform created from interpolation of a set of data points.
Parameters:
duration
Section titled “ duration
”int)
–The waveform duration (in ns).
values
Section titled “ values
”ArrayLike)
–Values of the interpolation points. Must be a list of castable to float or a parametrized object.
times
Section titled “ times
”ArrayLike, default:None)
–Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.
interpolator
Section titled “ interpolator
”str, default:'PchipInterpolator')
–The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = "PchipInterpolator", **interpolator_kwargs: Any,): """Initializes a new Interpolated waveform.""" super().__init__(duration) self._values = np.array(values, dtype=float) if times: # fractional times in [0,1] if any([(ft < 0) or (ft > 1) for ft in times]): raise ValueError("All values in `times` must be in [0,1].") self._times = np.array(times, dtype=float) if len(times) != len(self._values): raise ValueError( "Arguments `values` and `times` must be arrays of the same length." ) else: self._times = np.linspace(0, 1, num=len(self._values))
if interpolator not in self._valid_interpolators: raise ValueError( f"Invalid interpolator '{interpolator}', only " "accepts: " + ", ".join(self._valid_interpolators) ) self._interpolator = interpolator self._interpolator_kwargs = interpolator_kwargs
interp_cls = getattr(interpolate, interpolator) self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
MockDevice()
Section titled “
MockDevice()
”A virtual device for unconstrained prototyping.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
PiecewiseLinear(durations: list | tuple, values: list | tuple)
Section titled “
PiecewiseLinear(durations: list | tuple, values: list | tuple)
”A piecewise linear waveform.
Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.
Parameters:
durations
Section titled “ durations
”list | tuple)
–list or tuple of N duration values.
values
Section titled “ values
”list | tuple)
–list or tuple of N+1 waveform values.
Methods:
-
function–Identifies the right waveform in the composition and evaluates it at time t.
-
max–Get the maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
durations(list[float]) –Returns the list of durations of each individual waveform.
-
n_waveforms(int) –Returns the number of waveforms.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
-
times(list[float]) –Returns the list of times when each individual waveform starts.
-
waveforms(list[Waveform]) –Returns a list of the individual waveforms.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, durations: list | tuple, values: list | tuple,) -> None: if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))): raise TypeError( "A PiecewiseLinear waveform requires a list or tuple of durations and values." )
if len(durations) + 1 != len(values) or len(durations) == 1: raise ValueError( "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2." )
for duration in durations: if duration == 0.0: raise ValueError("A PiecewiseLinear interval cannot have zero duration.")
self.values = values
wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]
super().__init__(*wfs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
durations: list[float]
property
Section titled “
durations: list[float]
property
”Returns the list of durations of each individual waveform.
n_waveforms: int
property
Section titled “
n_waveforms: int
property
”Returns the number of waveforms.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
times: list[float]
property
Section titled “
times: list[float]
property
”Returns the list of times when each individual waveform starts.
waveforms: list[Waveform]
property
Section titled “
waveforms: list[Waveform]
property
”Returns a list of the individual waveforms.
function(t: float) -> float
Section titled “
function(t: float) -> float
”Identifies the right waveform in the composition and evaluates it at time t.
Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float: """Identifies the right waveform in the composition and evaluates it at time t.""" idx = np.searchsorted(self.times, t, side="right") - 1 if idx == -1: return 0.0 if idx == self.n_waveforms: if t == self.times[-1]: idx = idx - 1 else: return 0.0
local_t = t - self.times[idx] value: float = self.waveforms[idx](local_t) return value
max() -> float
Section titled “
max() -> float
”Get the maximum value of the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the maximum value of the waveform.""" return max([wf.max() for wf in self.waveforms])
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
QuantumProgram(register: Register, drive: Drive)
Section titled “
QuantumProgram(register: Register, drive: Drive)
”A program representing a Sequence acting on a Register of qubits.
Parameters:
register
Section titled “ register
”Register)
–the register of qubits, defining their positions.
drive
Section titled “ drive
”Drive)
–the drive acting on qubits, defining amplitude, detuning and phase.
Methods:
-
compile_to–Compiles the quantum program for execution on a specific device.
Attributes:
-
compiled_sequence(Sequence) –The Pulser sequence compiled to a specific device.
-
drive(Drive) –The driving waveforms.
-
is_compiled(bool) –Check if the program has been compiled.
-
register(Register) –The register of qubits.
Source code in qoolqit/program.py
def __init__( self, register: Register, drive: Drive,) -> None:
if not isinstance(register, Register): raise TypeError("`register` must be of type Register.") self._register = register if not isinstance(drive, Drive): raise TypeError("`drive` must be of type Drive.") 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.
drive: Drive
property
Section titled “
drive: Drive
property
”The driving waveforms.
is_compiled: bool
property
Section titled “
is_compiled: bool
property
”Check if the program has been compiled.
register: Register
property
Section titled “
register: Register
property
”The register of qubits.
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
Section titled “
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
”Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints.
- CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Parameters:
device
Section titled “ device
”Device)
–The target device for compilation.
profile
Section titled “ profile
”CompilerProfile, default:MAX_ENERGY)
–The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises:
-
CompilationError–If the compilation fails due to device constraints.
Source code in qoolqit/program.py
def compile_to( self, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None,) -> None: """Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints. - CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Args: device: The target device for compilation. profile: The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY. device_max_duration_ratio: Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises: CompilationError: If the compilation fails due to device constraints. """
if device_max_duration_ratio is not None: if device._max_duration is None: raise ValueError( "Cannot set `device_max_duration_ratio` because the target device " "does not have a maximum allowed duration." ) if not (0 < device_max_duration_ratio <= 1): raise ValueError( "`device_max_duration_ratio` must be between 0 and 1, " f"got {device_max_duration_ratio} instead." )
compiler = SequenceCompiler( self.register, self.drive, device, profile, device_max_duration_ratio ) self._device = device self._compiled_sequence = compiler.compile_sequence()
Ramp(duration: float, initial_value: float, final_value: float)
Section titled “
Ramp(duration: float, initial_value: float, final_value: float)
”A ramp that linearly interpolates between an initial and final value.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
initial_value
Section titled “ initial_value
”float)
–the initial value at t = 0.
final_value
Section titled “ final_value
”float)
–the final value at t = duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, initial_value: float, final_value: float,) -> None: super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Register(qubits: dict)
Section titled “
Register(qubits: dict)
”The Register in QoolQit, representing a set of qubits with coordinates.
Parameters:
qubits
Section titled “ qubits
”dict)
–a dictionary of qubits and respective coordinates {q: (x, y), ...}.
Methods:
-
distances–Distance between each qubit pair.
-
draw–Draw the register.
-
from_coordinates–Initializes a Register from a list of coordinates.
-
from_graph–Initializes a Register from a graph that has coordinates.
-
interactions–Interaction 1/r^6 between each qubit pair.
-
max_radial_distance–Maximum radial distance between all qubits.
-
min_distance–Minimum distance between all qubit pairs.
-
radial_distances–Radial distance of each qubit from the origin.
Attributes:
-
n_qubits(int) –Number of qubits in the Register.
-
qubits(dict) –Returns the dictionary of qubits and respective coordinates.
-
qubits_ids(list) –Returns the qubit keys.
Source code in qoolqit/register.py
def __init__(self, qubits: dict) -> None: """Default constructor for the Register.
Arguments: qubits: a dictionary of qubits and respective coordinates {q: (x, y), ...}. """ if not isinstance(qubits, dict): raise TypeError( "Register must be initialized with a dictionary of " "qubits and respective coordinates {q: (x, y), ...}." )
self._qubits: dict = qubits
n_qubits: int
property
Section titled “
n_qubits: int
property
”Number of qubits in the Register.
qubits: dict
property
Section titled “
qubits: dict
property
”Returns the dictionary of qubits and respective coordinates.
qubits_ids: list
property
Section titled “
qubits_ids: list
property
”Returns the qubit keys.
distances() -> dict
Section titled “
distances() -> dict
”Distance between each qubit pair.
Source code in qoolqit/register.py
def distances(self) -> dict: """Distance between each qubit pair.""" pairs = all_node_pairs(list(self.qubits.keys())) return distances(self.qubits, pairs)
draw(return_fig: bool = False) -> Figure | None
Section titled “
draw(return_fig: bool = False) -> Figure | None
”Draw the register.
Parameters:
return_fig
Section titled “ return_fig
”bool, default:False)
–boolean argument to return the matplotlib figure.
Source code in qoolqit/register.py
def draw(self, return_fig: bool = False) -> Figure | None: """Draw the register.
Arguments: return_fig: boolean argument to return the matplotlib figure. """ fig, ax = plt.subplots(1, 1, figsize=(4, 4), dpi=150) ax.set_aspect("equal")
x_coords, y_coords = zip(*self.qubits.values()) x_min, x_max = min(x_coords), max(x_coords) y_min, y_max = min(y_coords), max(y_coords)
grid_x_min, grid_x_max = min(-1, x_min), max(1, x_max) grid_y_min, grid_y_max = min(-1, y_min), max(1, y_max)
grid_scale = ceil(max(grid_x_max - grid_x_min, grid_y_max - grid_y_min))
ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7) ax.set_axisbelow(True) ax.set_xlabel("x") ax.set_ylabel("y")
eps = 0.05 * grid_scale ax.set_xlim(grid_x_min - eps, grid_x_max + eps) ax.set_ylim(grid_y_min - eps, grid_y_max + eps)
possible_multiples = [0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0] grid_multiple = min(possible_multiples, key=lambda x: abs(x - grid_scale / 8)) majorLocatorX = MultipleLocator(grid_multiple) majorLocatorY = MultipleLocator(grid_multiple) ax.xaxis.set_major_locator(majorLocatorX) ax.yaxis.set_major_locator(majorLocatorY)
ax.scatter(x_coords, y_coords, s=50, color="darkgreen")
ax.tick_params(axis="both", which="both", labelbottom=True, labelleft=True, labelsize=8)
if return_fig: plt.close() return fig else: return None
from_coordinates(coords: list) -> Register
classmethod
Section titled “
from_coordinates(coords: list) -> Register
classmethod
”Initializes a Register from a list of coordinates.
Parameters:
coords
Section titled “ coords
”list)
–a list of coordinates [(x, y), ...]
Source code in qoolqit/register.py
@classmethoddef from_coordinates(cls, coords: list) -> Register: """Initializes a Register from a list of coordinates.
Arguments: coords: a list of coordinates [(x, y), ...] """ if not isinstance(coords, list): raise TypeError( "Register must be initialized with a dictionary of qubit and coordinates." ) coords_dict = {i: pos for i, pos in enumerate(coords)} return cls(coords_dict)
from_graph(graph: DataGraph) -> Register
classmethod
Section titled “
from_graph(graph: DataGraph) -> Register
classmethod
”Initializes a Register from a graph that has coordinates.
Parameters:
graph
Section titled “ graph
”DataGraph)
–a DataGraph instance.
Source code in qoolqit/register.py
@classmethoddef from_graph(cls, graph: DataGraph) -> Register: """Initializes a Register from a graph that has coordinates.
Arguments: graph: a DataGraph instance. """
if not graph.has_coords: raise ValueError("Initializing a register from a graph requires node coordinates.")
if len(graph.nodes) == 0: raise ValueError("Trying to initialize a register from an empty graph.")
return cls(graph.coords)
interactions() -> dict
Section titled “
interactions() -> dict
”Interaction 1/r^6 between each qubit pair.
Source code in qoolqit/register.py
def interactions(self) -> dict: """Interaction 1/r^6 between each qubit pair.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
max_radial_distance() -> float
Section titled “
max_radial_distance() -> float
”Maximum radial distance between all qubits.
Source code in qoolqit/register.py
def max_radial_distance(self) -> float: """Maximum radial distance between all qubits.""" max_radial_distance: float = max(self.radial_distances().values()) return max_radial_distance
min_distance() -> float
Section titled “
min_distance() -> float
”Minimum distance between all qubit pairs.
Source code in qoolqit/register.py
def min_distance(self) -> float: """Minimum distance between all qubit pairs.""" distance: float = min(self.distances().values()) return distance
radial_distances() -> dict
Section titled “
radial_distances() -> dict
”Radial distance of each qubit from the origin.
Source code in qoolqit/register.py
def radial_distances(self) -> dict: """Radial distance of each qubit from the origin.""" return radial_distances(self.qubits)
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
Section titled “
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
”Compiles a QoolQit Register and Drive to a Device.
Parameters:
register
Section titled “ register
”Register)
–the QoolQit Register.
drive
Section titled “ drive
”Drive)
–the QoolQit Drive.
device
Section titled “ device
”Device)
–the QoolQit Device.
profile
Section titled “ profile
”CompilerProfile)
–the CompilerProfile to use.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–optionally set the program duration to a fraction of the device's maximum allowed duration.
Source code in qoolqit/execution/sequence_compiler.py
def __init__( self, register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None,) -> None: """Initializes the compiler.
Args: register: the QoolQit Register. drive: the QoolQit Drive. device: the QoolQit Device. profile: the CompilerProfile to use. device_max_duration_ratio: optionally set the program duration to a fraction of the device's maximum allowed duration. """
self._register = register self._drive = drive self._device = device self._target_device = device._device self._profile = profile self._device_max_duration_ratio = device_max_duration_ratio self._compilation_function: Callable = basic_compilation
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
Section titled “
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
”An arbitrary sine over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
amplitude
Section titled “ amplitude
”float, default:1.0)
–the amplitude of the sine wave.
omega
Section titled “ omega
”float, default:1.0)
–the frequency of the sine wave.
float, default:0.0)
–the phase of the sine wave.
shift
Section titled “ shift
”float, default:0.0)
–the vertical shift of the sine wave.
Methods:
-
max–Get the approximate maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0,) -> None: super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
max() -> float
Section titled “
max() -> float
”Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._max is None: self._approximate_min_max() return cast(float, self._max)
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
available_default_devices() -> None
Section titled “
available_default_devices() -> None
”Show the default available devices in QooQit.
Source code in qoolqit/devices/device.py
def available_default_devices() -> None: """Show the default available devices in QooQit.""" for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()): dev.info()
devices
Section titled “
devices
”Modules:
-
device– -
unit_converter–
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:
-
available_default_devices–Show the default available devices in QooQit.
AnalogDevice()
Section titled “
AnalogDevice()
”A realistic device for analog sequence execution.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
Section titled “
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
”QoolQit Device wrapper around a Pulser BaseDevice.
Parameters:
pulser_device
Section titled “ pulser_device
”BaseDevice)
–a BaseDevice to build the QoolQit device from.
default_converter
Section titled “ default_converter
”Optional[UnitConverter], default:None)
–optional unit converter to handle unit conversion.
Examples:
From Pulser device:
qoolqit_device = Device(pulser_device=pulser_device)From remote Pulser device:
from pulser_pasqal import PasqalCloudfrom qoolqit import Device
# Fetch the remote device from the connectionconnection = PasqalCloud()pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Devicefresnel_device = Device(pulser_device=PulserFresnelDevice)From custom Pulser device:
from dataclasses import replacefrom pulser import AnalogDevicefrom qoolqit import Device
# Converting the pulser Device object in a VirtualDevice objectVirtualAnalog = AnalogDevice.to_virtual()# Replacing desired valuesModdedAnalogDevice = replace( VirtualAnalog, max_radial_distance=100, max_sequence_duration=7000 )
# Wrap a Pulser device object into a QoolQit Devicemod_analog_device = Device(pulser_device=ModdedAnalogDevice)Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__( self, pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None,) -> None:
if not isinstance(pulser_device, BaseDevice): raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")
# Store it for all subsequent lookups self._pulser_device: BaseDevice = pulser_device self._name: str = self._pulser_device.name
# Physical constants / channel & limit lookups (assumes 'rydberg_global' channel) self._C6 = self._pulser_device.interaction_coeff self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period # Relevant limits from the underlying device (float or None) self._max_duration = self._pulser_device.max_sequence_duration self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp self._upper_amp = self._max_amp or 4 * math.pi self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning self._min_distance = self._pulser_device.min_atom_distance self._lower_distance = self._min_distance or 5.0 self._max_radial_distance = self._pulser_device.max_radial_distance
# ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6 self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6
# layouts self._requires_layout = self._pulser_device.requires_layout
if default_converter is not None: # Snapshot the caller-provided factors so reset() reproduces them exactly. t0, e0, d0 = default_converter.factors self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter( self._C6, t0, e0, d0 ) else: self._default_factory = lambda: UnitConverter.from_distance( self._C6, self._lower_distance )
self.reset_converter()
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
DigitalAnalogDevice()
Section titled “
DigitalAnalogDevice()
”A device with digital and analog capabilities.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
MockDevice()
Section titled “
MockDevice()
”A virtual device for unconstrained prototyping.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
connection
Section titled “ connection
”RemoteConnection)
–connection object to fetch the available devices.
str)
–The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
available_default_devices() -> None
Section titled “
available_default_devices() -> None
”Show the default available devices in QooQit.
Source code in qoolqit/devices/device.py
def available_default_devices() -> None: """Show the default available devices in QooQit.""" for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()): dev.info()
device
Section titled “
device
”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:
-
available_default_devices–Show the default available devices in QooQit.
AnalogDevice()
Section titled “
AnalogDevice()
”A realistic device for analog sequence execution.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
-
(connectionRemoteConnection) –connection object to fetch the available devices.
-
(namestr) –The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
Section titled “
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)
”QoolQit Device wrapper around a Pulser BaseDevice.
Parameters:
pulser_device
Section titled “ pulser_device
”BaseDevice)
–a BaseDevice to build the QoolQit device from.
default_converter
Section titled “ default_converter
”Optional[UnitConverter], default:None)
–optional unit converter to handle unit conversion.
Examples:
From Pulser device:
qoolqit_device = Device(pulser_device=pulser_device)From remote Pulser device:
from pulser_pasqal import PasqalCloudfrom qoolqit import Device
# Fetch the remote device from the connectionconnection = PasqalCloud()pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]
# Wrap a Pulser device object into a QoolQit Devicefresnel_device = Device(pulser_device=PulserFresnelDevice)From custom Pulser device:
from dataclasses import replacefrom pulser import AnalogDevicefrom qoolqit import Device
# Converting the pulser Device object in a VirtualDevice objectVirtualAnalog = AnalogDevice.to_virtual()# Replacing desired valuesModdedAnalogDevice = replace( VirtualAnalog, max_radial_distance=100, max_sequence_duration=7000 )
# Wrap a Pulser device object into a QoolQit Devicemod_analog_device = Device(pulser_device=ModdedAnalogDevice)Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__( self, pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None,) -> None:
if not isinstance(pulser_device, BaseDevice): raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")
# Store it for all subsequent lookups self._pulser_device: BaseDevice = pulser_device self._name: str = self._pulser_device.name
# Physical constants / channel & limit lookups (assumes 'rydberg_global' channel) self._C6 = self._pulser_device.interaction_coeff self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period # Relevant limits from the underlying device (float or None) self._max_duration = self._pulser_device.max_sequence_duration self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp self._upper_amp = self._max_amp or 4 * math.pi self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning self._min_distance = self._pulser_device.min_atom_distance self._lower_distance = self._min_distance or 5.0 self._max_radial_distance = self._pulser_device.max_radial_distance
# ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6 self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6
# layouts self._requires_layout = self._pulser_device.requires_layout
if default_converter is not None: # Snapshot the caller-provided factors so reset() reproduces them exactly. t0, e0, d0 = default_converter.factors self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter( self._C6, t0, e0, d0 ) else: self._default_factory = lambda: UnitConverter.from_distance( self._C6, self._lower_distance )
self.reset_converter()
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
-
(connectionRemoteConnection) –connection object to fetch the available devices.
-
(namestr) –The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
DigitalAnalogDevice()
Section titled “
DigitalAnalogDevice()
”A device with digital and analog capabilities.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
-
(connectionRemoteConnection) –connection object to fetch the available devices.
-
(namestr) –The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
MockDevice()
Section titled “
MockDevice()
”A virtual device for unconstrained prototyping.
Methods:
-
from_connection–Return the specified device from the selected device from a connection.
-
info–Show the device short description and constraints.
-
reset_converter–Resets the unit converter to the default one.
-
set_distance_unit–Changes the unit converter according to a reference distance unit.
-
set_energy_unit–Changes the unit converter according to a reference energy unit.
Attributes:
-
specs(dict[str, float | None]) –Return the device specification constraints.
Source code in qoolqit/devices/device.py
def __init__(self) -> None: super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None]
property
Section titled “
specs: dict[str, float | None]
property
”Return the device specification constraints.
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
Section titled “
from_connection(connection: RemoteConnection, name: str) -> Device
classmethod
”Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with
the connection.fetch_available_devices() method.
Parameters:
-
(connectionRemoteConnection) –connection object to fetch the available devices.
-
(namestr) –The name of the desired device.
Returns:
-
Device(Device) –The requested device.
Raises:
-
ValueError–If the requested device is not available through the provided connection.
Example:
from pulser_pasqal import PasqalCloudfresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")Source code in qoolqit/devices/device.py
@classmethoddef from_connection(cls, connection: RemoteConnection, name: str) -> Device: """Return the specified device from the selected device from a connection.
Available devices through the provided connection are can be seen with the `connection.fetch_available_devices()` method.
Args: connection (RemoteConnection): connection object to fetch the available devices. name (str): The name of the desired device.
Returns: Device: The requested device.
Raises: ValueError: If the requested device is not available through the provided connection.
Example: ```python from pulser_pasqal import PasqalCloud fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL") ``` """ available_remote_devices = connection.fetch_available_devices() if name not in available_remote_devices: raise ValueError(f"Device {name} is not available through the provided connection.") pulser_device = available_remote_devices[name] return cls(pulser_device=pulser_device)
info() -> None
Section titled “
info() -> None
”Show the device short description and constraints.
Source code in qoolqit/devices/device.py
def info(self) -> None: """Show the device short description and constraints.""" print(self)
reset_converter() -> None
Section titled “
reset_converter() -> None
”Resets the unit converter to the default one.
Source code in qoolqit/devices/device.py
def reset_converter(self) -> None: """Resets the unit converter to the default one.""" # Create a NEW converter so mutations don't persist. self._converter = self._default_converter
set_distance_unit(distance: float) -> None
Section titled “
set_distance_unit(distance: float) -> None
”Changes the unit converter according to a reference distance unit.
Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None: """Changes the unit converter according to a reference distance unit.""" self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None
Section titled “
set_energy_unit(energy: float) -> None
”Changes the unit converter according to a reference energy unit.
Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None: """Changes the unit converter according to a reference energy unit.""" self.converter.factors = self.converter.factors_from_energy(energy)
available_default_devices() -> None
Section titled “
available_default_devices() -> None
”Show the default available devices in QooQit.
Source code in qoolqit/devices/device.py
def available_default_devices() -> None: """Show the default available devices in QooQit.""" for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()): dev.info()
unit_converter
Section titled “
unit_converter
”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.
energy: float
instance-attribute
Section titled “
energy: float
instance-attribute
”Distance conversion factor.
factors: tuple[float, ...]
property
writable
Section titled “
factors: tuple[float, ...]
property
writable
”Return the current conversion factors set.
time: float
instance-attribute
Section titled “
time: float
instance-attribute
”Energy conversion factor.
factors_from_distance(distance: float) -> tuple[float, ...]
Section titled “
factors_from_distance(distance: float) -> 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) -> 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) -> 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) -> UnitConverter
classmethod
”Instantiate from a reference C6 value and a reference distance unit.
Source code in qoolqit/devices/unit_converter.py
@classmethoddef 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) -> UnitConverter
classmethod
”Instantiate from a reference C6 value and a reference energy unit.
Source code in qoolqit/devices/unit_converter.py
@classmethoddef 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) -> UnitConverter
classmethod
”Instantiate from a reference C6 value and a reference time unit.
Source code in qoolqit/devices/unit_converter.py
@classmethoddef 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) -> 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)
drive
Section titled “
drive
”Classes:
-
Drive–The drive Hamiltonian acting over a duration.
-
WeightedDetuning–A weighted detuning.
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:
amplitude
Section titled “ amplitude
”Waveform | None, default:None)
–waveform representing Ω(t) in the drive Hamiltonian.
detuning
Section titled “ detuning
”Waveform | None, default:None)
–waveform representing δ(t) in the drive Hamiltonian.
phase
Section titled “ phase
”float, default:0.0)
–phase value ɸ for the amplitude term.
weighted_detunings
Section titled “ weighted_detunings
”list[WeightedDetuning] | None, default:None)
–additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.
Attributes:
-
amplitude(Waveform) –The amplitude waveform in the drive.
-
detuning(Waveform) –The detuning waveform in the drive.
-
phase(float) –The phase value in the drive.
-
weighted_detunings(Sequence[WeightedDetuning]) –Detunings applied to individual qubits.
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 []
amplitude: Waveform
property
Section titled “
amplitude: Waveform
property
”The amplitude waveform in the drive.
detuning: Waveform
property
Section titled “
detuning: Waveform
property
”The detuning waveform in the drive.
phase: float
property
Section titled “
phase: float
property
”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.
waveform: Waveform
instance-attribute
Section titled “
waveform: Waveform
instance-attribute
”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.
embedding
Section titled “
embedding
”Collection of graph and matrix embedding algorithms.
Modules:
Classes:
-
BaseEmbedder–Abstract base class for all embedders.
-
Blade–A matrix to graph embedder using the BLaDE algorithm.
-
BladeConfig–Configuration parameters to embed with BLaDE.
-
EmbedderConfig–Base abstract dataclass for all embedding algorithm configurations.
-
GraphToGraphEmbedder–A family of embedders that map a graph to a graph.
-
InteractionEmbedder–A matrix to graph embedder using the interaction embedding algorithm.
-
InteractionEmbedderConfig–Configuration parameters for the interaction embedding.
-
MatrixToGraphEmbedder–A family of embedders that map a matrix to a graph.
-
SpringLayoutConfig–Configuration parameters for the spring-layout embedding.
-
SpringLayoutEmbedder–A graph to graph embedder using the spring layout algorithm.
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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”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) -> 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
@abstractmethoddef 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) -> 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
Section titled “ 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
@abstractmethoddef 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:
config
Section titled “ config
”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)
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: np.ndarray) -> DataGraph
Section titled “
embed(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.
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
BladeConfigdataclass. -
dict–Returns the dataclass as a dictionary.
__post_init__(device: Device | None) -> None
Section titled “
__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
Section titled “ 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
Section titled “
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)
EmbedderConfig()
dataclass
Section titled “
EmbedderConfig()
dataclass
”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.
dict() -> dict
Section titled “
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)
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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”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
InteractionEmbedder()
Section titled “
InteractionEmbedder()
”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())
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”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 = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08)
dataclass
”Configuration parameters for the interaction embedding.
Methods:
-
dict–Returns the dataclass as a dictionary.
dict() -> dict
Section titled “
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)
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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”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.
dict() -> dict
Section titled “
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)
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)
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”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
algorithms
Section titled “
algorithms
”Modules:
Classes:
-
BladeConfig–Configuration parameters to embed with BLaDE.
-
InteractionEmbedderConfig–Configuration parameters for the interaction embedding.
-
SpringLayoutConfig–Configuration parameters for the spring-layout embedding.
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
BladeConfigdataclass. -
dict–Returns the dataclass as a dictionary.
__post_init__(device: Device | None) -> None
Section titled “
__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:
-
(deviceDevice) –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
Section titled “
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)
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08)
dataclass
Section titled “
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08)
dataclass
”Configuration parameters for the interaction embedding.
Methods:
-
dict–Returns the dataclass as a dictionary.
dict() -> dict
Section titled “
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)
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
Section titled “
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
Section titled “
blade
”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
BladeConfigdataclass. -
dict–Returns the dataclass as a dictionary.
__post_init__(device: Device | None) -> NonePost 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:
-
(deviceDevice) –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_distancedict() -> dictReturns 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
Section titled “
blade
”Classes:
-
BladeConfig–Configuration parameters to embed with BLaDE.
Functions:
-
blade–Embed an interaction matrix or QUBO with the BLaDE algorithm.
-
default_compute_max_distance_to_walk–Default function with rapid then slow decrease to zero of the walking distance.
-
default_compute_ratio_step_factors–Default function to decrease the ratio slightly too low and then increase.
-
update_positions–Compute vector moves to adjust node positions toward target interactions.
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
BladeConfigdataclass. -
dict–Returns the dataclass as a dictionary.
__post_init__(device: Device | None) -> NonePost 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:
-
(deviceDevice) –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_distancedict() -> dictReturns 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.ndarrayEmbed 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 positionsdefault_compute_max_distance_to_walk(progress: float, max_radial_dist: float | None) -> floatDefault 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) -> floatDefault 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.ndarrayCompute 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
drawing
Section titled “
drawing
”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.ndarrayReturn 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) -> NoneCoords 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()
interaction_embedding
Section titled “
interaction_embedding
”Classes:
-
InteractionEmbedderConfig–Configuration parameters for the interaction embedding.
Functions:
-
interaction_embedding–Matrix embedding into the interaction term of the Rydberg Analog Model.
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08)
dataclass
Section titled “
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08)
dataclass
”Configuration parameters for the interaction embedding.
Methods:
-
dict–Returns the dataclass as a dictionary.
dict() -> dictReturns 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) -> 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:
-
(matrixndarray) –the matrix to embed.
-
(methodstr) –the method used by scipy.minimize.
-
(maxiterint) –maximum number of iterations.
-
(tolfloat) –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
spring_layout_embedding
Section titled “
spring_layout_embedding
”Classes:
-
SpringLayoutConfig–Configuration parameters for the spring-layout embedding.
Functions:
-
spring_layout_embedding–Force-directed embedding, wrapping
nx.spring_layout.
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() -> dictReturns 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) -> 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:
-
(graphDataGraph) –the graph to embed according to its edge weights.
-
(iterationsint) –maximum number of iterations to take.
-
(thresholdfloat) –Threshold for relative error in node position changes. The iteration stops if force-displacement is below this threshold.
-
(seedint | 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
base_embedder
Section titled “
base_embedder
”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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”Validates the input, runs the embedding algorithm, and validates the output.
Parameters:
-
(dataInDataType) –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) -> 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:
-
(dataInDataType) –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
@abstractmethoddef 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) -> 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:
-
(resultOutDataType) –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
@abstractmethoddef 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. """ ...
EmbedderConfig()
dataclass
Section titled “
EmbedderConfig()
dataclass
”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.
dict() -> dict
Section titled “
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)
graph_embedder
Section titled “
graph_embedder
”Classes:
-
GraphToGraphEmbedder–A family of embedders that map a graph to a graph.
-
SpringLayoutEmbedder–A graph to graph embedder using the spring layout algorithm.
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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”Validates the input, runs the embedding algorithm, and validates the output.
Parameters:
-
(dataInDataType) –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)
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”Validates the input, runs the embedding algorithm, and validates the output.
Parameters:
-
(dataInDataType) –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
matrix_embedder
Section titled “
matrix_embedder
”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:
config
Section titled “ config
”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)
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: np.ndarray) -> DataGraph
Section titled “
embed(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.
Parameters:
-
(datandarray) –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
InteractionEmbedder()
Section titled “
InteractionEmbedder()
”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())
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”Validates the input, runs the embedding algorithm, and validates the output.
Parameters:
-
(dataInDataType) –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:
algorithm
Section titled “ algorithm
”Callable)
–a callable to the algorithm function.
config
Section titled “ config
”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
algorithm: Callable
property
Section titled “
algorithm: Callable
property
”Returns the callable to the embedding algorithm.
config: ConfigType
property
Section titled “
config: ConfigType
property
”Returns the config for the embedding algorithm.
info: str
property
Section titled “
info: str
property
”Prints info about the embedding algorithm.
embed(data: InDataType) -> OutDataType
Section titled “
embed(data: InDataType) -> OutDataType
”Validates the input, runs the embedding algorithm, and validates the output.
Parameters:
-
(dataInDataType) –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
exceptions
Section titled “
exceptions
”Classes:
-
CompilationError–An error raised when attempting to compile a program into a Pulser Sequence.
CompilationError
Section titled “
CompilationError
”An error raised when attempting to compile a program into a Pulser Sequence.
execution
Section titled “
execution
”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:
backend_type
Section titled “ backend_type
”type, default:QutipBackendV2)
–backend type. Must be a subtype of pulser.backend.EmulatorBackend.
emulation_config
Section titled “ emulation_config
”EmulationConfig, default:None)
–optional configuration object emulators.
num_shots
Section titled “ num_shots
”int, default:None)
–number of bitstring samples to collect from the final quantum state.
Examples:
from qoolqit.execution import LocalEmulator, BackendTypebackend = LocalEmulator(backend_type=BackendType.QutipBackendV2)result = backend.run(program)Methods:
-
default_emulation_config–Return a unique emulation config for all emulators.
-
run–Run a compiled QuantumProgram and return the results.
-
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[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)
default_emulation_config() -> EmulationConfig
Section titled “
default_emulation_config() -> EmulationConfig
”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) -> 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) -> EmulationConfig
”Returns a valid config for emulator backends, if needed.
Parameters:
emulation_config
Section titled “ 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
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:
connection
Section titled “ connection
”RemoteConnection)
–Authenticated connection to the remote QPU backend.
num_shots
Section titled “ num_shots
”int | None, default:None)
–Number of bitstring samples to collect from the final quantum state.
Examples:
Using Pasqal Cloud:
from pulser_pasqal import PasqalCloudfrom 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 PulserQLMConnectionfrom 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) -> 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
Section titled “ 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) -> RemoteResults
”Submit a compiled quantum program to the QPU and return a result handler.
Parameters:
program
Section titled “ program
”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) -> 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
@staticmethoddef 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:
backend_type
Section titled “ backend_type
”type, default:EmuFreeBackendV2)
–backend type. Must be a subtype of
pulser_pasqal.backends.RemoteEmulatorBackend.
connection
Section titled “ connection
”RemoteConnection)
–connection to execute the program on remote backends.
emulation_config
Section titled “ emulation_config
”EmulationConfig, default:None)
–optional configuration object emulators.
num_shots
Section titled “ num_shots
”int, default:None)
–number of bitstring samples to collect from the final quantum state.
Examples:
from pulser_pasqal import PasqalCloudfrom qoolqit.execution import RemoteEmulator, BackendTypeconnection = PasqalCloud(username=..., password=..., project_id=...)backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)remote_results = backend.submit(program)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)
default_emulation_config() -> EmulationConfig
Section titled “
default_emulation_config() -> EmulationConfig
”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) -> 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) -> 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
Section titled “ program
”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) -> 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
@staticmethoddef 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) -> EmulationConfig
”Returns a valid config for emulator backends, if needed.
Parameters:
emulation_config
Section titled “ 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
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
Section titled “
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
”Compiles a QoolQit Register and Drive to a Device.
Parameters:
register
Section titled “ register
”Register)
–the QoolQit Register.
drive
Section titled “ drive
”Drive)
–the QoolQit Drive.
device
Section titled “ device
”Device)
–the QoolQit Device.
profile
Section titled “ profile
”CompilerProfile)
–the CompilerProfile to use.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–optionally set the program duration to a fraction of the device's maximum allowed duration.
Source code in qoolqit/execution/sequence_compiler.py
def __init__( self, register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None,) -> None: """Initializes the compiler.
Args: register: the QoolQit Register. drive: the QoolQit Drive. device: the QoolQit Device. profile: the CompilerProfile to use. device_max_duration_ratio: optionally set the program duration to a fraction of the device's maximum allowed duration. """
self._register = register self._drive = drive self._device = device self._target_device = device._device self._profile = profile self._device_max_duration_ratio = device_max_duration_ratio self._compilation_function: Callable = basic_compilation
backends
Section titled “
backends
”Classes:
-
LocalEmulator–Run QoolQit
QuantumPrograms on a Pasqal local emulator backends. -
PulserEmulatorBackend–Base Emulator class.
-
PulserRemoteBackend– -
QPU–Execute QoolQit QuantumPrograms on Pasqal quantum processing units.
-
RemoteEmulator–Run QoolQit
QuantumPrograms on a Pasqal remote emulator backends.
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:
backend_type
Section titled “ backend_type
”type, default:QutipBackendV2)
–backend type. Must be a subtype of pulser.backend.EmulatorBackend.
emulation_config
Section titled “ emulation_config
”EmulationConfig, default:None)
–optional configuration object emulators.
num_shots
Section titled “ num_shots
”int, default:None)
–number of bitstring samples to collect from the final quantum state.
Examples:
from qoolqit.execution import LocalEmulator, BackendTypebackend = LocalEmulator(backend_type=BackendType.QutipBackendV2)result = backend.run(program)Methods:
-
default_emulation_config–Return a unique emulation config for all emulators.
-
run–Run a compiled QuantumProgram and return the results.
-
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[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)
default_emulation_config() -> EmulationConfig
Section titled “
default_emulation_config() -> EmulationConfig
”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) -> 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) -> EmulationConfig
”Returns a valid config for emulator backends, if needed.
Parameters:
-
(emulation_configEmulationConfig | 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:
num_shots
Section titled “ num_shots
”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:
-
default_emulation_config–Return a unique emulation config for all emulators.
-
validate_emulation_config–Returns a valid config for emulator backends, if needed.
Source code in qoolqit/execution/backends.py
def __init__(self, num_shots: int | None = None) -> None: self._num_shots = num_shots
default_emulation_config() -> EmulationConfig
Section titled “
default_emulation_config() -> EmulationConfig
”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) -> EmulationConfig
”Returns a valid config for emulator backends, if needed.
Parameters:
-
(emulation_configEmulationConfig | 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
PulserRemoteBackend
Section titled “
PulserRemoteBackend
”Methods:
-
validate_connection–Validate the required connection to instantiate a RemoteBackend.
validate_connection(connection: RemoteConnection) -> RemoteConnection
staticmethod
Section titled “
validate_connection(connection: RemoteConnection) -> 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
@staticmethoddef 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:
connection
Section titled “ connection
”RemoteConnection)
–Authenticated connection to the remote QPU backend.
num_shots
Section titled “ num_shots
”int | None, default:None)
–Number of bitstring samples to collect from the final quantum state.
Examples:
Using Pasqal Cloud:
from pulser_pasqal import PasqalCloudfrom 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 PulserQLMConnectionfrom 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) -> 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:
-
(programQuantumProgram) –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) -> RemoteResults
”Submit a compiled quantum program to the QPU and return a result handler.
Parameters:
-
(programQuantumProgram) –The compiled quantum program to execute on the QPU.
-
(waitbool, 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) -> 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
@staticmethoddef 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:
backend_type
Section titled “ backend_type
”type, default:EmuFreeBackendV2)
–backend type. Must be a subtype of
pulser_pasqal.backends.RemoteEmulatorBackend.
connection
Section titled “ connection
”RemoteConnection)
–connection to execute the program on remote backends.
emulation_config
Section titled “ emulation_config
”EmulationConfig, default:None)
–optional configuration object emulators.
num_shots
Section titled “ num_shots
”int, default:None)
–number of bitstring samples to collect from the final quantum state.
Examples:
from pulser_pasqal import PasqalCloudfrom qoolqit.execution import RemoteEmulator, BackendTypeconnection = PasqalCloud(username=..., password=..., project_id=...)backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)remote_results = backend.submit(program)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)
default_emulation_config() -> EmulationConfig
Section titled “
default_emulation_config() -> EmulationConfig
”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) -> 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) -> 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:
-
(programQuantumProgram) –the compiled quantum program to run.
-
(waitbool, 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) -> 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
@staticmethoddef 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) -> EmulationConfig
”Returns a valid config for emulator backends, if needed.
Parameters:
-
(emulation_configEmulationConfig | 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
compilation_functions
Section titled “
compilation_functions
”Classes:
-
CompilerProfile–Enum for the different compilation profiles.
-
WaveformConverter–Convert a QoolQit waveform into a equivalent Pulser waveform.
Functions:
-
basic_compilation–Compiles a QoolQit program to a PulserSequence.
CompilerProfile
Section titled “
CompilerProfile
”Enum for the different compilation profiles.
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) -> 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) -> 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
Section titled “ register
”Register)
–QoolQit Register.
drive
Section titled “ drive
”Drive)
–QoolQit Drive.
device
Section titled “ device
”Device)
–QoolQit Device.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–optionally set the program duration to a fraction of the device's maximum allowed duration.
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
sequence_compiler
Section titled “
sequence_compiler
”Classes:
-
SequenceCompiler–Compiles a QoolQit Register and Drive to a Device.
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
Section titled “
SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)
”Compiles a QoolQit Register and Drive to a Device.
Parameters:
register
Section titled “ register
”Register)
–the QoolQit Register.
drive
Section titled “ drive
”Drive)
–the QoolQit Drive.
device
Section titled “ device
”Device)
–the QoolQit Device.
profile
Section titled “ profile
”CompilerProfile)
–the CompilerProfile to use.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–optionally set the program duration to a fraction of the device's maximum allowed duration.
Source code in qoolqit/execution/sequence_compiler.py
def __init__( self, register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None,) -> None: """Initializes the compiler.
Args: register: the QoolQit Register. drive: the QoolQit Drive. device: the QoolQit Device. profile: the CompilerProfile to use. device_max_duration_ratio: optionally set the program duration to a fraction of the device's maximum allowed duration. """
self._register = register self._drive = drive self._device = device self._target_device = device._device self._profile = profile self._device_max_duration_ratio = device_max_duration_ratio self._compilation_function: Callable = basic_compilation
graphs
Section titled “
graphs
”Graph creation and manipulation in QoolQit.
Modules:
-
base_graph– -
data_graph– -
utils–
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.
BaseGraph(edges: Iterable = [])
Section titled “
BaseGraph(edges: Iterable = [])
”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:
edges
Section titled “ edges
”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()
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”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) -> dict
”Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Parameters:
edge_list
Section titled “ edge_list
”Iterable | None, default:None)
–set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
(Axes | None, default:None)
–Axes object to draw on. If None, uses the current Axes.
**kwargs
Section titled “ **kwargs
”Any, default:{})
–keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
coords
Section titled “ coords
”list | dict)
–list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
nodes
Section titled “ nodes
”Iterable)
–set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = min(self.distances(self.all_node_pairs).values()) elif connected: distance = min(self.distances(self.sorted_edges).values()) else: distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
Section titled “
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
scaling
Section titled “ scaling
”float | None, default:None)
–value to scale by.
spacing
Section titled “ spacing
”float | None, default:None)
–value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Parameters:
radius
Section titled “ 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))
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
radius
Section titled “ radius
”float)
–the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
DataGraph(edges: Iterable = [])
Section titled “
DataGraph(edges: Iterable = [])
”The main graph structure to represent problem data.
Parameters:
edges
Section titled “ edges
”Iterable, default:[])
–set of edge tuples (i, j)
Methods:
-
circle–Constructs a circle graph, with the respective coordinates.
-
distances–Returns a dictionary of distances for a given set of edges.
-
draw–Draw the graph.
-
from_coordinates–Construct a base graph from a set of coordinates.
-
from_matrix–Constructs a graph from a symmetric square matrix.
-
from_nodes–Construct a base graph from a set of nodes.
-
from_nx–Convert a NetworkX Graph object into a QoolQit graph instance.
-
from_pyg–Convert a PyTorch Geometric Data object into a DataGraph instance.
-
heavy_hexagonal–Constructs a heavy-hexagonal lattice graph, with respective coordinates.
-
hexagonal–Constructs a hexagonal lattice graph, with respective coordinates.
-
interactions–Rydberg model interaction 1/r^6 between pair of nodes.
-
is_ud_graph–Check if the graph is unit-disk.
-
line–Constructs a line graph, with the respective coordinates.
-
max_distance–Returns the maximum distance in the graph.
-
min_distance–Returns the minimum distance in the graph.
-
random_er–Constructs an Erdős–Rényi random graph.
-
random_ud–Constructs a random unit-disk graph.
-
rescale_coords–Rescales the node coordinates by a factor.
-
set_ud_edges–Reset the set of edges to be equal to the set of unit-disk edges.
-
square–Constructs a square lattice graph, with respective coordinates.
-
to_pyg–Convert the DataGraph to a PyTorch Geometric Data object.
-
triangular–Constructs a triangular lattice graph, with respective coordinates.
-
ud_edges–Returns the set of edges given by the intersection of circles of a given radius.
-
ud_radius_range–Return the range (R_min, R_max) where the graph is unit-disk.
Attributes:
-
all_node_pairs(set) –Return a list of all possible node pairs in the graph.
-
coords(dict) –Return the dictionary of node coordinates.
-
edge_weights(dict) –Return the dictionary of edge weights.
-
has_coords(bool) –Check if the graph has coordinates.
-
has_edge_weights(bool) –Check if the graph has edge weights.
-
has_edges(bool) –Check if the graph has edges.
-
has_node_weights(bool) –Check if the graph has node weights.
-
node_weights(dict) –Return the dictionary of node weights.
-
sorted_edges(set) –Returns the set of edges (u, v) such that (u < v).
Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None: """ Default constructor for the BaseGraph.
Arguments: edges: set of edge tuples (i, j) """ super().__init__(edges)
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
edge_weights: dict
property
writable
Section titled “
edge_weights: dict
property
writable
”Return the dictionary of edge weights.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
node_weights: dict
property
writable
Section titled “
node_weights: dict
property
writable
”Return the dictionary of node weights.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”Returns the set of edges (u, v) such that (u < v).
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
Section titled “
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
”Constructs a circle graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
center
Section titled “ center
”tuple, default:(0.0, 0.0))
–point (x, y) to set as the center of the graph.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef circle( cls, n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0),) -> DataGraph: """Constructs a circle graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. center: point (x, y) to set as the center of the graph. """
d_theta = (2.0 * np.pi) / n r = spacing / (2.0 * np.sin(np.pi / n)) theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n) coords = [ (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta)) ] edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)] graph = cls.from_coordinates(coords) graph.add_edges_from(edges) graph._reset_dicts() return graph
distances(edge_list: Iterable | None = None) -> dict
Section titled “
distances(edge_list: Iterable | None = None) -> dict
”Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Parameters:
edge_list
Section titled “ edge_list
”Iterable | None, default:None)
–set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
(Axes | None, default:None)
–Axes object to draw on. If None, uses the current Axes.
**kwargs
Section titled “ **kwargs
”Any, default:{})
–keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
coords
Section titled “ coords
”list | dict)
–list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_matrix(data: ArrayLike) -> DataGraph
classmethod
Section titled “
from_matrix(data: ArrayLike) -> DataGraph
classmethod
”Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Parameters:
(ArrayLike)
–symmetric square matrix.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_matrix(cls, data: ArrayLike) -> DataGraph: """Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Arguments: data: symmetric square matrix. """ if data.ndim != 2: raise ValueError("2D Matrix required.") if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32): raise ValueError("Matrix must be symmetric.")
diag = np.diag(data) n_nodes = len(diag) node_weights = {i: diag[i] for i in range(n_nodes)} if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32): node_weights = {i: None for i in range(n_nodes)} else: node_weights = {i: diag[i].item() for i in range(n_nodes)}
data[data <= ATOL_32] = 0.0 non_zero = data.nonzero() i_list = non_zero[0].tolist() j_list = non_zero[1].tolist()
edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j] edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}
graph = cls.from_nodes(range(n_nodes)) graph.add_edges_from(edge_list) graph.node_weights = node_weights graph.edge_weights = edge_weights return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
nodes
Section titled “ nodes
”Iterable)
–set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
Section titled “
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
”Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires torch_geometric. Uses to_networkx internally.
Default attributes copied (if present on data ):
- Node:
x,pos(posis also stored in_coords) - Edge:
edge_attr - Graph:
y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit weights (_node_weights, _edge_weights) are not
populated automatically — use the explicit parameters:
node_weights_attr: real-valued tensor of shape(N,)or(N, 1). Defaults toNone.edge_weights_attr: real-valued tensor of shape(E,)or(E, 1)whereE = edge_index.shape[1](directed count). Defaults toNone.
The weight attribute is also stored as a regular node/edge attribute.
Parameters:
(Data)
–PyTorch Geometric Data object to convert.
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to copy (beyond x and pos).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to copy (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to copy (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str | None, default:None)
–Data attribute to use as node weights.
edge_weights_attr
Section titled “ edge_weights_attr
”str | None, default:None)
–Data attribute to use as edge weights.
Returns:
-
DataGraph–DataGraph with
_coords,_node_weights,_edge_weights -
DataGraph–populated where applicable.
Raises:
-
ImportError–if
torch_geometricis not installed. -
TypeError–if
datais not atorch_geometric.data.Datainstance, or if a weight attribute is not atorch.Tensor. -
AttributeError–if a specified weight attribute is missing.
-
ValueError–if a weight tensor has an incompatible shape or size.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_pyg( cls, data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None,) -> DataGraph: """Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires ``torch_geometric``. Uses ``to_networkx`` internally.
**Default attributes copied (if present on** ``data`` **):**
- Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``) - Edge: ``edge_attr`` - Graph: ``y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit weights** (``_node_weights``, ``_edge_weights``) are not populated automatically — use the explicit parameters:
- ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or ``(N, 1)``. Defaults to ``None``. - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count). Defaults to ``None``.
The weight attribute is also stored as a regular node/edge attribute.
Arguments: data: PyTorch Geometric Data object to convert. node_attrs: extra node attributes to copy (beyond x and pos). edge_attrs: extra edge attributes to copy (beyond edge_attr). graph_attrs: extra graph-level attributes to copy (beyond y). node_weights_attr: Data attribute to use as node weights. edge_weights_attr: Data attribute to use as edge weights.
Returns: DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights`` populated where applicable.
Raises: ImportError: if ``torch_geometric`` is not installed. TypeError: if ``data`` is not a ``torch_geometric.data.Data`` instance, or if a weight attribute is not a ``torch.Tensor``. AttributeError: if a specified weight attribute is missing. ValueError: if a weight tensor has an incompatible shape or size. """ try: from torch_geometric.data import Data from torch_geometric.utils import to_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
if not isinstance(data, Data): raise TypeError("Input must be a torch_geometric.data.Data object.")
# Validate weight attrs early and keep the squeezed tensors node_tensor = ( cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node") if node_weights_attr is not None else None ) edge_tensor = ( cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge") if edge_weights_attr is not None else None )
# Select unique attributes and add default ones only if present in the data node_attrs_set = {k for k in {"x", "pos"} if k in data} if node_attrs is not None: node_attrs_set |= set(node_attrs) if node_weights_attr is not None: node_attrs_set.add(node_weights_attr)
edge_attrs_set = {k for k in {"edge_attr"} if k in data} if edge_attrs is not None: edge_attrs_set |= set(edge_attrs) if edge_weights_attr is not None: edge_attrs_set.add(edge_weights_attr)
graph_attrs_set = {k for k in {"y"} if k in data} if graph_attrs is not None: graph_attrs_set |= set(graph_attrs)
# Convert to NetworkX (undirected, no self-loops) nx_graph = to_networkx( data, node_attrs=list(node_attrs_set), edge_attrs=list(edge_attrs_set), graph_attrs=list(graph_attrs_set), to_undirected=True, remove_self_loops=True, )
# Build the DataGraph: edges carry their data, nodes carry their data graph = cls(nx_graph.edges(data=True)) graph.add_nodes_from(nx_graph.nodes(data=True)) graph.graph = nx_graph.graph
# Re-initialize QoolQit internal dicts for all nodes/edges graph._coords = {n: None for n in graph.nodes} graph._reset_dicts()
# pos → _coords (stored as list [x, y] by to_networkx) for node, node_data in nx_graph.nodes(data=True): if "pos" in node_data: graph._coords[node] = tuple(node_data["pos"]) # type: ignore[assignment]
# node_weights_attr → _node_weights if node_tensor is not None: for i in range(data.num_nodes): graph._node_weights[i] = node_tensor[i].item()
# edge_weights_attr → _edge_weights if edge_tensor is not None: seen: set = set() for idx in range(data.edge_index.shape[1]): u = int(data.edge_index[0, idx].item()) v = int(data.edge_index[1, idx].item()) key = (min(u, v), max(u, v)) if key not in seen: graph._edge_weights[key] = edge_tensor[idx].item() seen.add(key)
return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Notes
Source code in qoolqit/graphs/data_graph.py
@classmethoddef heavy_hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice.
Notes: The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site. """ G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True) pos_unit = nx.get_node_attributes(G_hex, "pos")
G_heavy = nx.Graph() scaling_factor = 2 * spacing
label_map = {} for old_label, (x, y) in pos_unit.items(): # Relabel to an even-integer grid to make space for midpoint nodes new_label = (2 * old_label[0], 2 * old_label[1]) label_map[old_label] = new_label
# Scale positions and add the node to the new graph new_pos = (x * scaling_factor, y * scaling_factor) G_heavy.add_node(new_label, pos=new_pos)
for u_old, v_old in G_hex.edges(): u_new, v_new = label_map[u_old], label_map[v_old]
mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)
pos_u = G_heavy.nodes[u_new]["pos"] pos_v = G_heavy.nodes[v_new]["pos"] mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)
G_heavy.add_node(mid_label, pos=mid_pos) G_heavy.add_edge(u_new, mid_label) G_heavy.add_edge(mid_label, v_new)
final_nodes = sorted(list(G_heavy.nodes()))
final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]
label_to_int = {label: i for i, label in enumerate(final_nodes)}
final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]
graph = cls.from_coordinates(final_coords) graph.add_edges_from(final_edges) graph._reset_dicts() return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a hexagonal lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of hexagons.
int)
–Number of columns of hexagons.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.hexagonal_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a line graph, with the respective coordinates.
Parameters:
(int)
–number of nodes.
spacing
Section titled “ spacing
”float, default:1.0)
–distance between each node.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef line(cls, n: int, spacing: float = 1.0) -> DataGraph: """Constructs a line graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. """ coords = [(i * spacing, 0.0) for i in range(n)] graph = cls.from_coordinates(coords) edges = [(i, i + 1) for i in range(0, n - 1)] graph.add_edges_from(edges) graph._reset_dicts() return graph
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
connected
Section titled “ connected
”bool | None, default:None)
–if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = min(self.distances(self.all_node_pairs).values()) elif connected: distance = min(self.distances(self.sorted_edges).values()) else: distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
Section titled “
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
”Constructs an Erdős–Rényi random graph.
Parameters:
(int)
–number of nodes.
float)
–probability that any two nodes connect.
int | None, default:None)
–random seed.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph: """Constructs an Erdős–Rényi random graph.
Arguments: n: number of nodes. p: probability that any two nodes connect. seed: random seed. """ base_graph = nx.erdos_renyi_graph(n, p, seed) graph = DataGraph.from_nodes(list(base_graph.nodes)) graph.add_edges_from(base_graph.edges) graph._reset_dicts() return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
Section titled “
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
”Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Parameters:
(int)
–number of nodes.
radius
Section titled “ radius
”float, default:1.0)
–radius to use for defining the unit-disk edges.
float | None, default:None)
–size of the square on which to sample the node coordinates.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_ud( cls, n: int, radius: float = 1.0, L: float | None = None,) -> DataGraph: """Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Arguments: n: number of nodes. radius: radius to use for defining the unit-disk edges. L: size of the square on which to sample the node coordinates. """ if L is None: L = (radius / 2) * ((np.pi * n) ** 0.5) coords = random_coords(n, L) graph = cls.from_coordinates(coords) edges = graph.ud_edges(radius) graph.add_edges_from(edges) graph._reset_dicts() return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
Section titled “
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
scaling
Section titled “ scaling
”float | None, default:None)
–value to scale by.
spacing
Section titled “ spacing
”float | None, default:None)
–value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None: """Reset the set of edges to be equal to the set of unit-disk edges.""" super().set_ud_edges(radius=radius) self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a square lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of square.
int)
–Number of columns of square.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef square( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a square lattice graph, with respective coordinates.
Arguments: m: Number of rows of square. n: Number of columns of square. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.grid_2d_graph(m, n) final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)] G = nx.convert_node_labels_to_integers(G)
graph = DataGraph.from_coordinates(final_coords) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
Section titled “
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
”Convert the DataGraph to a PyTorch Geometric Data object.
Requires torch_geometric. Uses from_networkx internally.
Default attributes exported (if present on the graph):
- Node
"x"→data.x; Edge"edge_attr"→data.edge_attr - Graph
"y"→data.y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit internal dicts exported when populated:
_coords→data.pos(float64, shape(N, 2))_node_weights→data.<node_weights_attr>(float64, shape(N,)). Defaults to"weight"._edge_weights→data.<edge_weights_attr>(float64, shape(2*E,)). Defaults to"edge_weight".
Parameters:
node_attrs
Section titled “ node_attrs
”Iterable[str] | None, default:None)
–extra node attributes to export (beyond x).
edge_attrs
Section titled “ edge_attrs
”Iterable[str] | None, default:None)
–extra edge attributes to export (beyond edge_attr).
graph_attrs
Section titled “ graph_attrs
”Iterable[str] | None, default:None)
–extra graph-level attributes to export (beyond y).
node_weights_attr
Section titled “ node_weights_attr
”str, default:'weight')
–Data attribute name for node weights.
Defaults to "weight".
edge_weights_attr
Section titled “ edge_weights_attr
”str, default:'edge_weight')
–Data attribute name for edge weights.
Defaults to "edge_weight".
Returns:
-
Data–PyTorch Geometric Data object.
Raises:
-
ImportError–if
torch_geometricis not installed.
Source code in qoolqit/graphs/data_graph.py
def to_pyg( self, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = "weight", edge_weights_attr: str = "edge_weight",) -> torch_geometric.data.Data: """Convert the DataGraph to a PyTorch Geometric Data object.
Requires ``torch_geometric``. Uses ``from_networkx`` internally.
**Default attributes exported (if present on the graph):**
- Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr`` - Graph ``"y"`` → ``data.y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit internal dicts exported when populated:**
- ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``) - ``_node_weights`` → ``data.`` (float64, shape ``(N,)``). Defaults to ``"weight"``. - ``_edge_weights`` → ``data.`` (float64, shape ``(2*E,)``). Defaults to ``"edge_weight"``.
Arguments: node_attrs: extra node attributes to export (beyond x). edge_attrs: extra edge attributes to export (beyond edge_attr). graph_attrs: extra graph-level attributes to export (beyond y). node_weights_attr: Data attribute name for node weights. Defaults to ``"weight"``. edge_weights_attr: Data attribute name for edge weights. Defaults to ``"edge_weight"``.
Returns: PyTorch Geometric Data object.
Raises: ImportError: if ``torch_geometric`` is not installed. """ try: import torch from torch_geometric.utils import from_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
node_attrs_set = set(node_attrs) if node_attrs else set() edge_attrs_set = set(edge_attrs) if edge_attrs else set() graph_attrs_list = list(graph_attrs) if graph_attrs else []
# Add default PyG attributes if present in the graph if any("x" in d for _, d in self.nodes(data=True)): node_attrs_set.add("x") if any("edge_attr" in d for _, _, d in self.edges(data=True)): edge_attrs_set.add("edge_attr") if "y" in self.graph: graph_attrs_list.append("y")
# Build a filtered copy with only the requested attributes filtered_graph = nx.Graph() filtered_graph.add_nodes_from(self.nodes()) filtered_graph.add_edges_from(self.edges())
for node, node_data in self.nodes(data=True): for key, value in node_data.items(): if key in node_attrs_set: filtered_graph.nodes[node][key] = value for u, v, edge_data in self.edges(data=True): for key, value in edge_data.items(): if key in edge_attrs_set: filtered_graph.edges[u, v][key] = value for attr in graph_attrs_list: if attr in self.graph: filtered_graph.graph[attr] = self.graph[attr]
data = from_networkx(filtered_graph)
# Export _coords → pos if self.has_coords: positions = [self._coords[n] for n in sorted(self.nodes())] data.pos = torch.tensor(positions, dtype=torch.float64)
# Export _node_weights → node_weights_attr if self.has_node_weights: weights = [self._node_weights[n] for n in sorted(self.nodes())] setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))
# Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index) if self.has_edge_weights: edge_weights: list[float] = [] for i in range(data.edge_index.shape[1]): u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item()) edge_key = (min(u, v), max(u, v)) edge_weights.append(float(self._edge_weights[edge_key])) # type: ignore[arg-type] setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))
return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a triangular lattice graph, with respective coordinates.
Parameters:
(int)
–Number of rows of triangles.
int)
–Number of columns of triangles.
spacing
Section titled “ spacing
”float, default:1.0)
–The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef triangular( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a triangular lattice graph, with respective coordinates.
Arguments: m: Number of rows of triangles. n: Number of columns of triangles. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.triangular_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
radius
Section titled “ radius
”float)
–the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
all_node_pairs(nodes: Iterable) -> set
Section titled “
all_node_pairs(nodes: Iterable) -> set
”Return all pairs of nodes (u, v) where u < v.
Parameters:
nodes
Section titled “ nodes
”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) -> dict
”Return a dictionary of edge distances.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
edge_list
Section titled “ edge_list
”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}
random_coords(n: int, L: float = 1.0) -> list
Section titled “
random_coords(n: int, L: float = 1.0) -> 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) -> 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) -> dict
”Scale the coordinates by a given value.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
scaling
Section titled “ scaling
”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) -> dict
”Spaces the coordinates so the minimum distance is equal to a set spacing.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
spacing
Section titled “ spacing
”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)
base_graph
Section titled “
base_graph
”Classes:
-
BaseGraph–The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.
BaseGraph(edges: Iterable = [])
Section titled “
BaseGraph(edges: Iterable = [])
”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:
edges
Section titled “ edges
”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()
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”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) -> 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_listIterable | None, default:None) –set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
-
(axAxes | None, default:None) –Axes object to draw on. If None, uses the current Axes.
-
(**kwargsAny, default:{}) –keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
-
(coordslist | dict) –list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
-
(nodesIterable) –set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
-
(connectedbool | None, default:None) –if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
-
(connectedbool | 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) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
-
(scalingfloat | None, default:None) –value to scale by.
-
(spacingfloat | None, default:None) –value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Parameters:
-
(radiusfloat) –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))
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
-
(radiusfloat) –the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
data_graph
Section titled “
data_graph
”Classes:
-
DataGraph–The main graph structure to represent problem data.
DataGraph(edges: Iterable = [])
Section titled “
DataGraph(edges: Iterable = [])
”The main graph structure to represent problem data.
Parameters:
edges
Section titled “ edges
”Iterable, default:[])
–set of edge tuples (i, j)
Methods:
-
circle–Constructs a circle graph, with the respective coordinates.
-
distances–Returns a dictionary of distances for a given set of edges.
-
draw–Draw the graph.
-
from_coordinates–Construct a base graph from a set of coordinates.
-
from_matrix–Constructs a graph from a symmetric square matrix.
-
from_nodes–Construct a base graph from a set of nodes.
-
from_nx–Convert a NetworkX Graph object into a QoolQit graph instance.
-
from_pyg–Convert a PyTorch Geometric Data object into a DataGraph instance.
-
heavy_hexagonal–Constructs a heavy-hexagonal lattice graph, with respective coordinates.
-
hexagonal–Constructs a hexagonal lattice graph, with respective coordinates.
-
interactions–Rydberg model interaction 1/r^6 between pair of nodes.
-
is_ud_graph–Check if the graph is unit-disk.
-
line–Constructs a line graph, with the respective coordinates.
-
max_distance–Returns the maximum distance in the graph.
-
min_distance–Returns the minimum distance in the graph.
-
random_er–Constructs an Erdős–Rényi random graph.
-
random_ud–Constructs a random unit-disk graph.
-
rescale_coords–Rescales the node coordinates by a factor.
-
set_ud_edges–Reset the set of edges to be equal to the set of unit-disk edges.
-
square–Constructs a square lattice graph, with respective coordinates.
-
to_pyg–Convert the DataGraph to a PyTorch Geometric Data object.
-
triangular–Constructs a triangular lattice graph, with respective coordinates.
-
ud_edges–Returns the set of edges given by the intersection of circles of a given radius.
-
ud_radius_range–Return the range (R_min, R_max) where the graph is unit-disk.
Attributes:
-
all_node_pairs(set) –Return a list of all possible node pairs in the graph.
-
coords(dict) –Return the dictionary of node coordinates.
-
edge_weights(dict) –Return the dictionary of edge weights.
-
has_coords(bool) –Check if the graph has coordinates.
-
has_edge_weights(bool) –Check if the graph has edge weights.
-
has_edges(bool) –Check if the graph has edges.
-
has_node_weights(bool) –Check if the graph has node weights.
-
node_weights(dict) –Return the dictionary of node weights.
-
sorted_edges(set) –Returns the set of edges (u, v) such that (u < v).
Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None: """ Default constructor for the BaseGraph.
Arguments: edges: set of edge tuples (i, j) """ super().__init__(edges)
all_node_pairs: set
property
Section titled “
all_node_pairs: set
property
”Return a list of all possible node pairs in the graph.
coords: dict
property
writable
Section titled “
coords: dict
property
writable
”Return the dictionary of node coordinates.
edge_weights: dict
property
writable
Section titled “
edge_weights: dict
property
writable
”Return the dictionary of edge weights.
has_coords: bool
property
Section titled “
has_coords: bool
property
”Check if the graph has coordinates.
Requires all nodes to have coordinates.
has_edge_weights: bool
property
Section titled “
has_edge_weights: bool
property
”Check if the graph has edge weights.
Requires all edges to have a weight.
has_edges: bool
property
Section titled “
has_edges: bool
property
”Check if the graph has edges.
has_node_weights: bool
property
Section titled “
has_node_weights: bool
property
”Check if the graph has node weights.
Requires all nodes to have a weight.
node_weights: dict
property
writable
Section titled “
node_weights: dict
property
writable
”Return the dictionary of node weights.
sorted_edges: set
property
Section titled “
sorted_edges: set
property
”Returns the set of edges (u, v) such that (u < v).
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
Section titled “
circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph
classmethod
”Constructs a circle graph, with the respective coordinates.
Parameters:
-
(nint) –number of nodes.
-
(spacingfloat, default:1.0) –distance between each node.
-
(centertuple, default:(0.0, 0.0)) –point (x, y) to set as the center of the graph.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef circle( cls, n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0),) -> DataGraph: """Constructs a circle graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. center: point (x, y) to set as the center of the graph. """
d_theta = (2.0 * np.pi) / n r = spacing / (2.0 * np.sin(np.pi / n)) theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n) coords = [ (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta)) ] edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)] graph = cls.from_coordinates(coords) graph.add_edges_from(edges) graph._reset_dicts() return graph
distances(edge_list: Iterable | None = None) -> dict
Section titled “
distances(edge_list: Iterable | None = None) -> dict
”Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Parameters:
-
(edge_listIterable | None, default:None) –set of edges.
Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict: """Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.
Arguments: edge_list: set of edges. """ if self.has_coords: if edge_list is None: edge_list = self.all_node_pairs elif len(edge_list) == 0: # type: ignore [arg-type] raise ValueError("Trying to compute distances for an empty edge list.") return distances(self.coords, edge_list) else: raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None
Section titled “
draw(ax: Axes | None = None, **kwargs: Any) -> None
”Draw the graph.
Uses the draw_networkx function from NetworkX.
Parameters:
-
(axAxes | None, default:None) –Axes object to draw on. If None, uses the current Axes.
-
(**kwargsAny, default:{}) –keyword-arguments to pass to draw_networkx.
Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None: """Draw the graph.
Uses the draw_networkx function from NetworkX.
Args: ax: Axes object to draw on. If None, uses the current Axes. **kwargs: keyword-arguments to pass to draw_networkx. """ if self.has_coords: if "hide_ticks" not in kwargs: kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None: ax = plt.gca() ax.set_xlabel("x") ax.set_ylabel("y") ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox ylim = ax.get_ylim() if (ylim[1] - ylim[0]) < 2: y_center = (ylim[0] + ylim[1]) / 2 ax.set_ylim(y_center - 1, y_center + 1) plt.tight_layout() else: nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
Section titled “
from_coordinates(coords: list | dict) -> BaseGraph
classmethod
”Construct a base graph from a set of coordinates.
Parameters:
-
(coordslist | dict) –list or dictionary of coordinate pairs.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_coordinates(cls, coords: list | dict) -> BaseGraph: """Construct a base graph from a set of coordinates.
Arguments: coords: list or dictionary of coordinate pairs. """ if isinstance(coords, list): nodes = list(range(len(coords))) coords_dict = {i: pos for i, pos in enumerate(coords)} elif isinstance(coords, dict): nodes = list(coords.keys()) coords_dict = coords graph = cls.from_nodes(nodes) graph._coords = coords_dict graph._reset_dicts() return graph
from_matrix(data: ArrayLike) -> DataGraph
classmethod
Section titled “
from_matrix(data: ArrayLike) -> DataGraph
classmethod
”Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Parameters:
-
(dataArrayLike) –symmetric square matrix.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_matrix(cls, data: ArrayLike) -> DataGraph: """Constructs a graph from a symmetric square matrix.
The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.
Arguments: data: symmetric square matrix. """ if data.ndim != 2: raise ValueError("2D Matrix required.") if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32): raise ValueError("Matrix must be symmetric.")
diag = np.diag(data) n_nodes = len(diag) node_weights = {i: diag[i] for i in range(n_nodes)} if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32): node_weights = {i: None for i in range(n_nodes)} else: node_weights = {i: diag[i].item() for i in range(n_nodes)}
data[data <= ATOL_32] = 0.0 non_zero = data.nonzero() i_list = non_zero[0].tolist() j_list = non_zero[1].tolist()
edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j] edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}
graph = cls.from_nodes(range(n_nodes)) graph.add_edges_from(edge_list) graph.node_weights = node_weights graph.edge_weights = edge_weights return graph
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
Section titled “
from_nodes(nodes: Iterable) -> BaseGraph
classmethod
”Construct a base graph from a set of nodes.
Parameters:
-
(nodesIterable) –set of nodes.
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nodes(cls, nodes: Iterable) -> BaseGraph: """Construct a base graph from a set of nodes.
Arguments: nodes: set of nodes. """ graph = cls() graph.add_nodes_from(nodes) graph._coords = {i: None for i in graph.nodes} graph._reset_dicts() return graph
from_nx(g: nx.Graph) -> BaseGraph
classmethod
Section titled “
from_nx(g: nx.Graph) -> BaseGraph
classmethod
”Convert a NetworkX Graph object into a QoolQit graph instance.
The input networkx.Graph graph must be defined only with the following allowed
Node attributes
Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethoddef from_nx(cls, g: nx.Graph) -> BaseGraph: """Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes: pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number. Edge attributes: weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes: - _node_weights : dict[node, float or None] - _edge_weights : dict[(u,v), float or None] - _coords : dict[node, (float,float) or None] """ if not isinstance(g, nx.Graph): raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g) num_nodes = len(g.nodes) num_edges = len(g.edges)
# validate node attributes for name, data in g.nodes.data(): unexpected_keys = set(data) - {"weight", "pos"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos") if node_pos: if len(node_pos) != num_nodes: raise ValueError("Node attribute `pos` must be defined for all nodes") for name, pos in node_pos.items(): is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2) is_real = all(isinstance(p, (float, int)) for p in pos) if not (is_2D & is_real): raise TypeError( f"In node {name} the `pos` attribute must be a 2D tuple/list" f" of real numbers, got {pos} instead." ) node_weights = nx.get_node_attributes(g, "weight") if node_weights: if len(node_weights) != num_nodes: raise ValueError("Node attribute `weight` must be defined for all nodes") for name, weight in node_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In node {name} the `weight` attribute must be a real number, " f"got {type(weight)} instead." "" )
# validate edge attributes for u, v, data in g.edges.data(): unexpected_keys = set(data) - {"weight"} if unexpected_keys: raise ValueError(f"{unexpected_keys} not allowed in edge attributes.") edge_weights = nx.get_edge_attributes(g, "weight") if edge_weights: if len(edge_weights) != num_edges: raise ValueError("Edge attribute `weight` must be defined for all edges") for name, weight in edge_weights.items(): if not isinstance(weight, (float, int)): raise TypeError( f"In edge {name}, the attribute `weight` must be a real number, " f"got {type(weight)} instead." )
# build the instance of the graph graph = cls() graph.add_nodes_from(g.nodes) graph.add_edges_from(g.edges) graph._node_weights = nx.get_node_attributes(g, "weight", default=None) graph._coords = nx.get_node_attributes(g, "pos", default=None) graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
Section titled “
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph
classmethod
”Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires torch_geometric. Uses to_networkx internally.
Default attributes copied (if present on data ):
- Node:
x,pos(posis also stored in_coords) - Edge:
edge_attr - Graph:
y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit weights (_node_weights, _edge_weights) are not
populated automatically — use the explicit parameters:
node_weights_attr: real-valued tensor of shape(N,)or(N, 1). Defaults toNone.edge_weights_attr: real-valued tensor of shape(E,)or(E, 1)whereE = edge_index.shape[1](directed count). Defaults toNone.
The weight attribute is also stored as a regular node/edge attribute.
Parameters:
-
(dataData) –PyTorch Geometric Data object to convert.
-
(node_attrsIterable[str] | None, default:None) –extra node attributes to copy (beyond x and pos).
-
(edge_attrsIterable[str] | None, default:None) –extra edge attributes to copy (beyond edge_attr).
-
(graph_attrsIterable[str] | None, default:None) –extra graph-level attributes to copy (beyond y).
-
(node_weights_attrstr | None, default:None) –Data attribute to use as node weights.
-
(edge_weights_attrstr | None, default:None) –Data attribute to use as edge weights.
Returns:
-
DataGraph–DataGraph with
_coords,_node_weights,_edge_weights -
DataGraph–populated where applicable.
Raises:
-
ImportError–if
torch_geometricis not installed. -
TypeError–if
datais not atorch_geometric.data.Datainstance, or if a weight attribute is not atorch.Tensor. -
AttributeError–if a specified weight attribute is missing.
-
ValueError–if a weight tensor has an incompatible shape or size.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef from_pyg( cls, data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None,) -> DataGraph: """Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires ``torch_geometric``. Uses ``to_networkx`` internally.
**Default attributes copied (if present on** ``data`` **):**
- Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``) - Edge: ``edge_attr`` - Graph: ``y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit weights** (``_node_weights``, ``_edge_weights``) are not populated automatically — use the explicit parameters:
- ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or ``(N, 1)``. Defaults to ``None``. - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count). Defaults to ``None``.
The weight attribute is also stored as a regular node/edge attribute.
Arguments: data: PyTorch Geometric Data object to convert. node_attrs: extra node attributes to copy (beyond x and pos). edge_attrs: extra edge attributes to copy (beyond edge_attr). graph_attrs: extra graph-level attributes to copy (beyond y). node_weights_attr: Data attribute to use as node weights. edge_weights_attr: Data attribute to use as edge weights.
Returns: DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights`` populated where applicable.
Raises: ImportError: if ``torch_geometric`` is not installed. TypeError: if ``data`` is not a ``torch_geometric.data.Data`` instance, or if a weight attribute is not a ``torch.Tensor``. AttributeError: if a specified weight attribute is missing. ValueError: if a weight tensor has an incompatible shape or size. """ try: from torch_geometric.data import Data from torch_geometric.utils import to_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
if not isinstance(data, Data): raise TypeError("Input must be a torch_geometric.data.Data object.")
# Validate weight attrs early and keep the squeezed tensors node_tensor = ( cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node") if node_weights_attr is not None else None ) edge_tensor = ( cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge") if edge_weights_attr is not None else None )
# Select unique attributes and add default ones only if present in the data node_attrs_set = {k for k in {"x", "pos"} if k in data} if node_attrs is not None: node_attrs_set |= set(node_attrs) if node_weights_attr is not None: node_attrs_set.add(node_weights_attr)
edge_attrs_set = {k for k in {"edge_attr"} if k in data} if edge_attrs is not None: edge_attrs_set |= set(edge_attrs) if edge_weights_attr is not None: edge_attrs_set.add(edge_weights_attr)
graph_attrs_set = {k for k in {"y"} if k in data} if graph_attrs is not None: graph_attrs_set |= set(graph_attrs)
# Convert to NetworkX (undirected, no self-loops) nx_graph = to_networkx( data, node_attrs=list(node_attrs_set), edge_attrs=list(edge_attrs_set), graph_attrs=list(graph_attrs_set), to_undirected=True, remove_self_loops=True, )
# Build the DataGraph: edges carry their data, nodes carry their data graph = cls(nx_graph.edges(data=True)) graph.add_nodes_from(nx_graph.nodes(data=True)) graph.graph = nx_graph.graph
# Re-initialize QoolQit internal dicts for all nodes/edges graph._coords = {n: None for n in graph.nodes} graph._reset_dicts()
# pos → _coords (stored as list [x, y] by to_networkx) for node, node_data in nx_graph.nodes(data=True): if "pos" in node_data: graph._coords[node] = tuple(node_data["pos"]) # type: ignore[assignment]
# node_weights_attr → _node_weights if node_tensor is not None: for i in range(data.num_nodes): graph._node_weights[i] = node_tensor[i].item()
# edge_weights_attr → _edge_weights if edge_tensor is not None: seen: set = set() for idx in range(data.edge_index.shape[1]): u = int(data.edge_index[0, idx].item()) v = int(data.edge_index[1, idx].item()) key = (min(u, v), max(u, v)) if key not in seen: graph._edge_weights[key] = edge_tensor[idx].item() seen.add(key)
return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Parameters:
-
(mint) –Number of rows of hexagons.
-
(nint) –Number of columns of hexagons.
-
(spacingfloat, default:1.0) –The distance between adjacent nodes on the final lattice.
Notes
Source code in qoolqit/graphs/data_graph.py
@classmethoddef heavy_hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice.
Notes: The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site. """ G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True) pos_unit = nx.get_node_attributes(G_hex, "pos")
G_heavy = nx.Graph() scaling_factor = 2 * spacing
label_map = {} for old_label, (x, y) in pos_unit.items(): # Relabel to an even-integer grid to make space for midpoint nodes new_label = (2 * old_label[0], 2 * old_label[1]) label_map[old_label] = new_label
# Scale positions and add the node to the new graph new_pos = (x * scaling_factor, y * scaling_factor) G_heavy.add_node(new_label, pos=new_pos)
for u_old, v_old in G_hex.edges(): u_new, v_new = label_map[u_old], label_map[v_old]
mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)
pos_u = G_heavy.nodes[u_new]["pos"] pos_v = G_heavy.nodes[v_new]["pos"] mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)
G_heavy.add_node(mid_label, pos=mid_pos) G_heavy.add_edge(u_new, mid_label) G_heavy.add_edge(mid_label, v_new)
final_nodes = sorted(list(G_heavy.nodes()))
final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]
label_to_int = {label: i for i, label in enumerate(final_nodes)}
final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]
graph = cls.from_coordinates(final_coords) graph.add_edges_from(final_edges) graph._reset_dicts() return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a hexagonal lattice graph, with respective coordinates.
Parameters:
-
(mint) –Number of rows of hexagons.
-
(nint) –Number of columns of hexagons.
-
(spacingfloat, default:1.0) –The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef hexagonal( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a hexagonal lattice graph, with respective coordinates.
Arguments: m: Number of rows of hexagons. n: Number of columns of hexagons. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.hexagonal_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
interactions() -> dict
Section titled “
interactions() -> dict
”Rydberg model interaction 1/r^6 between pair of nodes.
Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict: """Rydberg model interaction 1/r^6 between pair of nodes.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool
Section titled “
is_ud_graph() -> bool
”Check if the graph is unit-disk.
Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool: """Check if the graph is unit-disk.""" try: self.ud_radius_range() return True except ValueError: return False
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
line(n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a line graph, with the respective coordinates.
Parameters:
-
(nint) –number of nodes.
-
(spacingfloat, default:1.0) –distance between each node.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef line(cls, n: int, spacing: float = 1.0) -> DataGraph: """Constructs a line graph, with the respective coordinates.
Arguments: n: number of nodes. spacing: distance between each node. """ coords = [(i * spacing, 0.0) for i in range(n)] graph = cls.from_coordinates(coords) edges = [(i, i + 1) for i in range(0, n - 1)] graph.add_edges_from(edges) graph._reset_dicts() return graph
max_distance(connected: bool | None = None) -> float
Section titled “
max_distance(connected: bool | None = None) -> float
”Returns the maximum distance in the graph.
Parameters:
-
(connectedbool | None, default:None) –if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float: """Returns the maximum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = max(self.distances(self.all_node_pairs).values()) elif connected: distance = max(self.distances(self.sorted_edges).values()) else: distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
min_distance(connected: bool | None = None) -> float
Section titled “
min_distance(connected: bool | None = None) -> float
”Returns the minimum distance in the graph.
Parameters:
-
(connectedbool | None, default:None) –if True/False, computes only over connected/disconnected nodes.
Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph.
Arguments: connected: if True/False, computes only over connected/disconnected nodes. """ distance: float if connected is None: distance = min(self.distances(self.all_node_pairs).values()) elif connected: distance = min(self.distances(self.sorted_edges).values()) else: distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values()) return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
Section titled “
random_er(n: int, p: float, seed: int | None = None) -> DataGraph
classmethod
”Constructs an Erdős–Rényi random graph.
Parameters:
-
(nint) –number of nodes.
-
(pfloat) –probability that any two nodes connect.
-
(seedint | None, default:None) –random seed.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph: """Constructs an Erdős–Rényi random graph.
Arguments: n: number of nodes. p: probability that any two nodes connect. seed: random seed. """ base_graph = nx.erdos_renyi_graph(n, p, seed) graph = DataGraph.from_nodes(list(base_graph.nodes)) graph.add_edges_from(base_graph.edges) graph._reset_dicts() return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
Section titled “
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph
classmethod
”Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Parameters:
-
(nint) –number of nodes.
-
(radiusfloat, default:1.0) –radius to use for defining the unit-disk edges.
-
(Lfloat | None, default:None) –size of the square on which to sample the node coordinates.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef random_ud( cls, n: int, radius: float = 1.0, L: float | None = None,) -> DataGraph: """Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Arguments: n: number of nodes. radius: radius to use for defining the unit-disk edges. L: size of the square on which to sample the node coordinates. """ if L is None: L = (radius / 2) * ((np.pi * n) ** 0.5) coords = random_coords(n, L) graph = cls.from_coordinates(coords) edges = graph.ud_edges(radius) graph.add_edges_from(edges) graph._reset_dicts() return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
Section titled “
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None
”Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Parameters:
-
(scalingfloat | None, default:None) –value to scale by.
-
(spacingfloat | None, default:None) –value to set as the minimum distance in the graph.
Source code in qoolqit/graphs/base_graph.py
def rescale_coords( self, *args: Any, scaling: float | None = None, spacing: float | None = None,) -> None: """Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments: scaling: value to scale by. spacing: value to set as the minimum distance in the graph. """ if self.has_coords: msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument." if (len(args) > 0) or (scaling is None and spacing is None): raise TypeError(msg) if scaling is None and spacing is not None: self._coords = space_coords(self._coords, spacing) elif spacing is None and scaling is not None: self._coords = scale_coords(self._coords, scaling) else: raise TypeError(msg) else: raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None
Section titled “
set_ud_edges(radius: float) -> None
”Reset the set of edges to be equal to the set of unit-disk edges.
Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None: """Reset the set of edges to be equal to the set of unit-disk edges.""" super().set_ud_edges(radius=radius) self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
square(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a square lattice graph, with respective coordinates.
Parameters:
-
(mint) –Number of rows of square.
-
(nint) –Number of columns of square.
-
(spacingfloat, default:1.0) –The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef square( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a square lattice graph, with respective coordinates.
Arguments: m: Number of rows of square. n: Number of columns of square. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.grid_2d_graph(m, n) final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)] G = nx.convert_node_labels_to_integers(G)
graph = DataGraph.from_coordinates(final_coords) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
Section titled “
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data
”Convert the DataGraph to a PyTorch Geometric Data object.
Requires torch_geometric. Uses from_networkx internally.
Default attributes exported (if present on the graph):
- Node
"x"→data.x; Edge"edge_attr"→data.edge_attr - Graph
"y"→data.y
Use node_attrs, edge_attrs, graph_attrs for extras.
QoolQit internal dicts exported when populated:
_coords→data.pos(float64, shape(N, 2))_node_weights→data.<node_weights_attr>(float64, shape(N,)). Defaults to"weight"._edge_weights→data.<edge_weights_attr>(float64, shape(2*E,)). Defaults to"edge_weight".
Parameters:
-
(node_attrsIterable[str] | None, default:None) –extra node attributes to export (beyond x).
-
(edge_attrsIterable[str] | None, default:None) –extra edge attributes to export (beyond edge_attr).
-
(graph_attrsIterable[str] | None, default:None) –extra graph-level attributes to export (beyond y).
-
(node_weights_attrstr, default:'weight') –Data attribute name for node weights. Defaults to
"weight". -
(edge_weights_attrstr, default:'edge_weight') –Data attribute name for edge weights. Defaults to
"edge_weight".
Returns:
-
Data–PyTorch Geometric Data object.
Raises:
-
ImportError–if
torch_geometricis not installed.
Source code in qoolqit/graphs/data_graph.py
def to_pyg( self, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = "weight", edge_weights_attr: str = "edge_weight",) -> torch_geometric.data.Data: """Convert the DataGraph to a PyTorch Geometric Data object.
Requires ``torch_geometric``. Uses ``from_networkx`` internally.
**Default attributes exported (if present on the graph):**
- Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr`` - Graph ``"y"`` → ``data.y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit internal dicts exported when populated:**
- ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``) - ``_node_weights`` → ``data.`` (float64, shape ``(N,)``). Defaults to ``"weight"``. - ``_edge_weights`` → ``data.`` (float64, shape ``(2*E,)``). Defaults to ``"edge_weight"``.
Arguments: node_attrs: extra node attributes to export (beyond x). edge_attrs: extra edge attributes to export (beyond edge_attr). graph_attrs: extra graph-level attributes to export (beyond y). node_weights_attr: Data attribute name for node weights. Defaults to ``"weight"``. edge_weights_attr: Data attribute name for edge weights. Defaults to ``"edge_weight"``.
Returns: PyTorch Geometric Data object.
Raises: ImportError: if ``torch_geometric`` is not installed. """ try: import torch from torch_geometric.utils import from_networkx except ImportError as e: raise ImportError("Please, install the `torch_geometric` package.") from e
node_attrs_set = set(node_attrs) if node_attrs else set() edge_attrs_set = set(edge_attrs) if edge_attrs else set() graph_attrs_list = list(graph_attrs) if graph_attrs else []
# Add default PyG attributes if present in the graph if any("x" in d for _, d in self.nodes(data=True)): node_attrs_set.add("x") if any("edge_attr" in d for _, _, d in self.edges(data=True)): edge_attrs_set.add("edge_attr") if "y" in self.graph: graph_attrs_list.append("y")
# Build a filtered copy with only the requested attributes filtered_graph = nx.Graph() filtered_graph.add_nodes_from(self.nodes()) filtered_graph.add_edges_from(self.edges())
for node, node_data in self.nodes(data=True): for key, value in node_data.items(): if key in node_attrs_set: filtered_graph.nodes[node][key] = value for u, v, edge_data in self.edges(data=True): for key, value in edge_data.items(): if key in edge_attrs_set: filtered_graph.edges[u, v][key] = value for attr in graph_attrs_list: if attr in self.graph: filtered_graph.graph[attr] = self.graph[attr]
data = from_networkx(filtered_graph)
# Export _coords → pos if self.has_coords: positions = [self._coords[n] for n in sorted(self.nodes())] data.pos = torch.tensor(positions, dtype=torch.float64)
# Export _node_weights → node_weights_attr if self.has_node_weights: weights = [self._node_weights[n] for n in sorted(self.nodes())] setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))
# Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index) if self.has_edge_weights: edge_weights: list[float] = [] for i in range(data.edge_index.shape[1]): u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item()) edge_key = (min(u, v), max(u, v)) edge_weights.append(float(self._edge_weights[edge_key])) # type: ignore[arg-type] setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))
return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
Section titled “
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph
classmethod
”Constructs a triangular lattice graph, with respective coordinates.
Parameters:
-
(mint) –Number of rows of triangles.
-
(nint) –Number of columns of triangles.
-
(spacingfloat, default:1.0) –The distance between adjacent nodes on the final lattice.
Source code in qoolqit/graphs/data_graph.py
@classmethoddef triangular( cls, m: int, n: int, spacing: float = 1.0,) -> DataGraph: """ Constructs a triangular lattice graph, with respective coordinates.
Arguments: m: Number of rows of triangles. n: Number of columns of triangles. spacing: The distance between adjacent nodes on the final lattice. """ G = nx.triangular_lattice_graph(m, n, with_positions=True) G = nx.convert_node_labels_to_integers(G) pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos) graph.add_edges_from(G.edges) graph._reset_dicts() return graph
ud_edges(radius: float) -> set
Section titled “
ud_edges(radius: float) -> set
”Returns the set of edges given by the intersection of circles of a given radius.
Parameters:
-
(radiusfloat) –the value
Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set: """Returns the set of edges given by the intersection of circles of a given radius.
Arguments: radius: the value """ if self.has_coords: return set(e for e, d in self.distances().items() if less_or_equal(d, radius)) else: raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple
Section titled “
ud_radius_range() -> tuple
”Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges
Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple: """Return the range (R_min, R_max) where the graph is unit-disk.
The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:
graph.ud_edges(radius = R) == graph.sorted edges """ if self.has_coords: n_edges = len(self.sorted_edges) if n_edges == 0: # If the graph is empty and has coordinates return (0.0, self.min_distance(connected=False)) elif n_edges == len(self.all_node_pairs): # If the graph is fully connected return (self.max_distance(connected=True), float("inf")) elif self.max_distance(connected=True) < self.min_distance(connected=False): return (self.max_distance(connected=True), self.min_distance(connected=False)) else: raise ValueError("Graph is not unit disk.") else: raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")
utils
Section titled “
utils
”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.
all_node_pairs(nodes: Iterable) -> set
Section titled “
all_node_pairs(nodes: Iterable) -> set
”Return all pairs of nodes (u, v) where u < v.
Parameters:
nodes
Section titled “ nodes
”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) -> dict
”Return a dictionary of edge distances.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
edge_list
Section titled “ edge_list
”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) -> 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)
radial_distances(coords: dict) -> dict
Section titled “
radial_distances(coords: dict) -> dict
”Return a dictionary of node distances from the origin.
Parameters:
coords
Section titled “ coords
”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()}
random_coords(n: int, L: float = 1.0) -> list
Section titled “
random_coords(n: int, L: float = 1.0) -> 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) -> 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) -> dict
”Scale the coordinates by a given value.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
scaling
Section titled “ scaling
”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) -> dict
”Spaces the coordinates so the minimum distance is equal to a set spacing.
Parameters:
coords
Section titled “ coords
”dict)
–dictionary of node coordinates.
spacing
Section titled “ spacing
”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)
program
Section titled “
program
”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
Section titled “ register
”Register)
–the register of qubits, defining their positions.
drive
Section titled “ drive
”Drive)
–the drive acting on qubits, defining amplitude, detuning and phase.
Methods:
-
compile_to–Compiles the quantum program for execution on a specific device.
Attributes:
-
compiled_sequence(Sequence) –The Pulser sequence compiled to a specific device.
-
drive(Drive) –The driving waveforms.
-
is_compiled(bool) –Check if the program has been compiled.
-
register(Register) –The register of qubits.
Source code in qoolqit/program.py
def __init__( self, register: Register, drive: Drive,) -> None:
if not isinstance(register, Register): raise TypeError("`register` must be of type Register.") self._register = register if not isinstance(drive, Drive): raise TypeError("`drive` must be of type Drive.") 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.
drive: Drive
property
Section titled “
drive: Drive
property
”The driving waveforms.
is_compiled: bool
property
Section titled “
is_compiled: bool
property
”Check if the program has been compiled.
register: Register
property
Section titled “
register: Register
property
”The register of qubits.
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
Section titled “
compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None
”Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints.
- CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Parameters:
device
Section titled “ device
”Device)
–The target device for compilation.
profile
Section titled “ profile
”CompilerProfile, default:MAX_ENERGY)
–The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.
device_max_duration_ratio
Section titled “ device_max_duration_ratio
”float | None, default:None)
–Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises:
-
CompilationError–If the compilation fails due to device constraints.
Source code in qoolqit/program.py
def compile_to( self, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None,) -> None: """Compiles the quantum program for execution on a specific device.
The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:
- CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints. - CompilerProfile.WORKING_POINT: .
Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.
- device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.
Args: device: The target device for compilation. profile: The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY. device_max_duration_ratio: Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.
Raises: CompilationError: If the compilation fails due to device constraints. """
if device_max_duration_ratio is not None: if device._max_duration is None: raise ValueError( "Cannot set `device_max_duration_ratio` because the target device " "does not have a maximum allowed duration." ) if not (0 < device_max_duration_ratio <= 1): raise ValueError( "`device_max_duration_ratio` must be between 0 and 1, " f"got {device_max_duration_ratio} instead." )
compiler = SequenceCompiler( self.register, self.drive, device, profile, device_max_duration_ratio ) self._device = device self._compiled_sequence = compiler.compile_sequence()
register
Section titled “
register
”Classes:
-
Register–The Register in QoolQit, representing a set of qubits with coordinates.
Register(qubits: dict)
Section titled “
Register(qubits: dict)
”The Register in QoolQit, representing a set of qubits with coordinates.
Parameters:
qubits
Section titled “ qubits
”dict)
–a dictionary of qubits and respective coordinates {q: (x, y), ...}.
Methods:
-
distances–Distance between each qubit pair.
-
draw–Draw the register.
-
from_coordinates–Initializes a Register from a list of coordinates.
-
from_graph–Initializes a Register from a graph that has coordinates.
-
interactions–Interaction 1/r^6 between each qubit pair.
-
max_radial_distance–Maximum radial distance between all qubits.
-
min_distance–Minimum distance between all qubit pairs.
-
radial_distances–Radial distance of each qubit from the origin.
Attributes:
-
n_qubits(int) –Number of qubits in the Register.
-
qubits(dict) –Returns the dictionary of qubits and respective coordinates.
-
qubits_ids(list) –Returns the qubit keys.
Source code in qoolqit/register.py
def __init__(self, qubits: dict) -> None: """Default constructor for the Register.
Arguments: qubits: a dictionary of qubits and respective coordinates {q: (x, y), ...}. """ if not isinstance(qubits, dict): raise TypeError( "Register must be initialized with a dictionary of " "qubits and respective coordinates {q: (x, y), ...}." )
self._qubits: dict = qubits
n_qubits: int
property
Section titled “
n_qubits: int
property
”Number of qubits in the Register.
qubits: dict
property
Section titled “
qubits: dict
property
”Returns the dictionary of qubits and respective coordinates.
qubits_ids: list
property
Section titled “
qubits_ids: list
property
”Returns the qubit keys.
distances() -> dict
Section titled “
distances() -> dict
”Distance between each qubit pair.
Source code in qoolqit/register.py
def distances(self) -> dict: """Distance between each qubit pair.""" pairs = all_node_pairs(list(self.qubits.keys())) return distances(self.qubits, pairs)
draw(return_fig: bool = False) -> Figure | None
Section titled “
draw(return_fig: bool = False) -> Figure | None
”Draw the register.
Parameters:
return_fig
Section titled “ return_fig
”bool, default:False)
–boolean argument to return the matplotlib figure.
Source code in qoolqit/register.py
def draw(self, return_fig: bool = False) -> Figure | None: """Draw the register.
Arguments: return_fig: boolean argument to return the matplotlib figure. """ fig, ax = plt.subplots(1, 1, figsize=(4, 4), dpi=150) ax.set_aspect("equal")
x_coords, y_coords = zip(*self.qubits.values()) x_min, x_max = min(x_coords), max(x_coords) y_min, y_max = min(y_coords), max(y_coords)
grid_x_min, grid_x_max = min(-1, x_min), max(1, x_max) grid_y_min, grid_y_max = min(-1, y_min), max(1, y_max)
grid_scale = ceil(max(grid_x_max - grid_x_min, grid_y_max - grid_y_min))
ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7) ax.set_axisbelow(True) ax.set_xlabel("x") ax.set_ylabel("y")
eps = 0.05 * grid_scale ax.set_xlim(grid_x_min - eps, grid_x_max + eps) ax.set_ylim(grid_y_min - eps, grid_y_max + eps)
possible_multiples = [0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0] grid_multiple = min(possible_multiples, key=lambda x: abs(x - grid_scale / 8)) majorLocatorX = MultipleLocator(grid_multiple) majorLocatorY = MultipleLocator(grid_multiple) ax.xaxis.set_major_locator(majorLocatorX) ax.yaxis.set_major_locator(majorLocatorY)
ax.scatter(x_coords, y_coords, s=50, color="darkgreen")
ax.tick_params(axis="both", which="both", labelbottom=True, labelleft=True, labelsize=8)
if return_fig: plt.close() return fig else: return None
from_coordinates(coords: list) -> Register
classmethod
Section titled “
from_coordinates(coords: list) -> Register
classmethod
”Initializes a Register from a list of coordinates.
Parameters:
coords
Section titled “ coords
”list)
–a list of coordinates [(x, y), ...]
Source code in qoolqit/register.py
@classmethoddef from_coordinates(cls, coords: list) -> Register: """Initializes a Register from a list of coordinates.
Arguments: coords: a list of coordinates [(x, y), ...] """ if not isinstance(coords, list): raise TypeError( "Register must be initialized with a dictionary of qubit and coordinates." ) coords_dict = {i: pos for i, pos in enumerate(coords)} return cls(coords_dict)
from_graph(graph: DataGraph) -> Register
classmethod
Section titled “
from_graph(graph: DataGraph) -> Register
classmethod
”Initializes a Register from a graph that has coordinates.
Parameters:
graph
Section titled “ graph
”DataGraph)
–a DataGraph instance.
Source code in qoolqit/register.py
@classmethoddef from_graph(cls, graph: DataGraph) -> Register: """Initializes a Register from a graph that has coordinates.
Arguments: graph: a DataGraph instance. """
if not graph.has_coords: raise ValueError("Initializing a register from a graph requires node coordinates.")
if len(graph.nodes) == 0: raise ValueError("Trying to initialize a register from an empty graph.")
return cls(graph.coords)
interactions() -> dict
Section titled “
interactions() -> dict
”Interaction 1/r^6 between each qubit pair.
Source code in qoolqit/register.py
def interactions(self) -> dict: """Interaction 1/r^6 between each qubit pair.""" return {p: 1.0 / (r**6) for p, r in self.distances().items()}
max_radial_distance() -> float
Section titled “
max_radial_distance() -> float
”Maximum radial distance between all qubits.
Source code in qoolqit/register.py
def max_radial_distance(self) -> float: """Maximum radial distance between all qubits.""" max_radial_distance: float = max(self.radial_distances().values()) return max_radial_distance
min_distance() -> float
Section titled “
min_distance() -> float
”Minimum distance between all qubit pairs.
Source code in qoolqit/register.py
def min_distance(self) -> float: """Minimum distance between all qubit pairs.""" distance: float = min(self.distances().values()) return distance
radial_distances() -> dict
Section titled “
radial_distances() -> dict
”Radial distance of each qubit from the origin.
Source code in qoolqit/register.py
def radial_distances(self) -> dict: """Radial distance of each qubit from the origin.""" return radial_distances(self.qubits)
waveforms
Section titled “
waveforms
”Modules:
-
base_waveforms– -
utils– -
waveforms–
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.
Blackman(duration: float, area: float)
Section titled “
Blackman(duration: float, area: float)
”A Blackman window of a specified duration and area under the curve.
Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration
See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window (external)
Parameters:
duration
Section titled “ duration
”float)
–The waveform duration.
float)
–The integral of the waveform.
Example
blackman_wf = Blackman(100.0, area=3.14)Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None: """Initializes a new Blackman waveform.""" super().__init__(duration, area=area)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Constant(duration: float, value: float)
Section titled “
Constant(duration: float, value: float)
”A constant waveform over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
value
Section titled “ value
”float)
–the value to take during the duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, value: float,) -> None: super().__init__(duration, value=value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
”An empty waveform.
Parameters:
duration
Section titled “ duration
”float)
–the total duration of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def __init__( self, duration: float, *args: float, **kwargs: float | np.ndarray,) -> None: """Initializes the Waveform.
Arguments: duration: the total duration of the waveform. """
if duration <= 0: raise ValueError("Duration needs to be a positive non-zero value.")
if len(args) > 0: raise ValueError( f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments" )
self._duration = duration self._params_dict = kwargs
self._max: float | None = None self._min: float | None = None
for key, value in kwargs.items(): setattr(self, key, value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
Section titled “
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
”A waveform created from interpolation of a set of data points.
Parameters:
duration
Section titled “ duration
”int)
–The waveform duration (in ns).
values
Section titled “ values
”ArrayLike)
–Values of the interpolation points. Must be a list of castable to float or a parametrized object.
times
Section titled “ times
”ArrayLike, default:None)
–Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.
interpolator
Section titled “ interpolator
”str, default:'PchipInterpolator')
–The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = "PchipInterpolator", **interpolator_kwargs: Any,): """Initializes a new Interpolated waveform.""" super().__init__(duration) self._values = np.array(values, dtype=float) if times: # fractional times in [0,1] if any([(ft < 0) or (ft > 1) for ft in times]): raise ValueError("All values in `times` must be in [0,1].") self._times = np.array(times, dtype=float) if len(times) != len(self._values): raise ValueError( "Arguments `values` and `times` must be arrays of the same length." ) else: self._times = np.linspace(0, 1, num=len(self._values))
if interpolator not in self._valid_interpolators: raise ValueError( f"Invalid interpolator '{interpolator}', only " "accepts: " + ", ".join(self._valid_interpolators) ) self._interpolator = interpolator self._interpolator_kwargs = interpolator_kwargs
interp_cls = getattr(interpolate, interpolator) self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
PiecewiseLinear(durations: list | tuple, values: list | tuple)
Section titled “
PiecewiseLinear(durations: list | tuple, values: list | tuple)
”A piecewise linear waveform.
Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.
Parameters:
durations
Section titled “ durations
”list | tuple)
–list or tuple of N duration values.
values
Section titled “ values
”list | tuple)
–list or tuple of N+1 waveform values.
Methods:
-
function–Identifies the right waveform in the composition and evaluates it at time t.
-
max–Get the maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
durations(list[float]) –Returns the list of durations of each individual waveform.
-
n_waveforms(int) –Returns the number of waveforms.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
-
times(list[float]) –Returns the list of times when each individual waveform starts.
-
waveforms(list[Waveform]) –Returns a list of the individual waveforms.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, durations: list | tuple, values: list | tuple,) -> None: if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))): raise TypeError( "A PiecewiseLinear waveform requires a list or tuple of durations and values." )
if len(durations) + 1 != len(values) or len(durations) == 1: raise ValueError( "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2." )
for duration in durations: if duration == 0.0: raise ValueError("A PiecewiseLinear interval cannot have zero duration.")
self.values = values
wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]
super().__init__(*wfs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
durations: list[float]
property
Section titled “
durations: list[float]
property
”Returns the list of durations of each individual waveform.
n_waveforms: int
property
Section titled “
n_waveforms: int
property
”Returns the number of waveforms.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
times: list[float]
property
Section titled “
times: list[float]
property
”Returns the list of times when each individual waveform starts.
waveforms: list[Waveform]
property
Section titled “
waveforms: list[Waveform]
property
”Returns a list of the individual waveforms.
function(t: float) -> float
Section titled “
function(t: float) -> float
”Identifies the right waveform in the composition and evaluates it at time t.
Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float: """Identifies the right waveform in the composition and evaluates it at time t.""" idx = np.searchsorted(self.times, t, side="right") - 1 if idx == -1: return 0.0 if idx == self.n_waveforms: if t == self.times[-1]: idx = idx - 1 else: return 0.0
local_t = t - self.times[idx] value: float = self.waveforms[idx](local_t) return value
max() -> float
Section titled “
max() -> float
”Get the maximum value of the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the maximum value of the waveform.""" return max([wf.max() for wf in self.waveforms])
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
Ramp(duration: float, initial_value: float, final_value: float)
Section titled “
Ramp(duration: float, initial_value: float, final_value: float)
”A ramp that linearly interpolates between an initial and final value.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
initial_value
Section titled “ initial_value
”float)
–the initial value at t = 0.
final_value
Section titled “ final_value
”float)
–the final value at t = duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, initial_value: float, final_value: float,) -> None: super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
Section titled “
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
”An arbitrary sine over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
amplitude
Section titled “ amplitude
”float, default:1.0)
–the amplitude of the sine wave.
omega
Section titled “ omega
”float, default:1.0)
–the frequency of the sine wave.
float, default:0.0)
–the phase of the sine wave.
shift
Section titled “ shift
”float, default:0.0)
–the vertical shift of the sine wave.
Methods:
-
max–Get the approximate maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0,) -> None: super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
max() -> float
Section titled “
max() -> float
”Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._max is None: self._approximate_min_max() return cast(float, self._max)
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
base_waveforms
Section titled “
base_waveforms
”Classes:
-
CompositeWaveform–Base class for composite waveforms.
-
Waveform–Base class for waveforms.
CompositeWaveform(*waveforms: Waveform)
Section titled “
CompositeWaveform(*waveforms: Waveform)
”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:
waveforms
Section titled “ waveforms
”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))
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
durations: list[float]
property
Section titled “
durations: list[float]
property
”Returns the list of durations of each individual waveform.
n_waveforms: int
property
Section titled “
n_waveforms: int
property
”Returns the number of waveforms.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
times: list[float]
property
Section titled “
times: list[float]
property
”Returns the list of times when each individual waveform starts.
waveforms: list[Waveform]
property
Section titled “
waveforms: list[Waveform]
property
”Returns a list of the individual waveforms.
function(t: float) -> float
Section titled “
function(t: float) -> float
”Identifies the right waveform in the composition and evaluates it at time t.
Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float: """Identifies the right waveform in the composition and evaluates it at time t.""" idx = np.searchsorted(self.times, t, side="right") - 1 if idx == -1: return 0.0 if idx == self.n_waveforms: if t == self.times[-1]: idx = idx - 1 else: return 0.0
local_t = t - self.times[idx] value: float = self.waveforms[idx](local_t) return value
max() -> float
Section titled “
max() -> float
”Get the maximum value of the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the maximum value of the waveform.""" return max([wf.max() for wf in self.waveforms])
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
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:
duration
Section titled “ duration
”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)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
function(t: float) -> float
abstractmethod
Section titled “
function(t: float) -> float
abstractmethod
”Evaluates the waveform function at a given time t.
Source code in qoolqit/waveforms/base_waveforms.py
@abstractmethoddef function(self, t: float) -> float: """Evaluates the waveform function at a given time t.""" ...
max() -> float
Section titled “
max() -> float
”Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._max is None: self._approximate_min_max() return cast(float, self._max)
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
utils
Section titled “
utils
”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]) -> 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
waveforms
Section titled “
waveforms
”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.
Blackman(duration: float, area: float)
Section titled “
Blackman(duration: float, area: float)
”A Blackman window of a specified duration and area under the curve.
Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration
See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window (external)
Parameters:
duration
Section titled “ duration
”float)
–The waveform duration.
float)
–The integral of the waveform.
Example
blackman_wf = Blackman(100.0, area=3.14)Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None: """Initializes a new Blackman waveform.""" super().__init__(duration, area=area)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Constant(duration: float, value: float)
Section titled “
Constant(duration: float, value: float)
”A constant waveform over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
value
Section titled “ value
”float)
–the value to take during the duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, value: float,) -> None: super().__init__(duration, value=value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
Section titled “
Delay(duration: float, *args: float, **kwargs: float | np.ndarray)
”An empty waveform.
Parameters:
duration
Section titled “ duration
”float)
–the total duration of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def __init__( self, duration: float, *args: float, **kwargs: float | np.ndarray,) -> None: """Initializes the Waveform.
Arguments: duration: the total duration of the waveform. """
if duration <= 0: raise ValueError("Duration needs to be a positive non-zero value.")
if len(args) > 0: raise ValueError( f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments" )
self._duration = duration self._params_dict = kwargs
self._max: float | None = None self._min: float | None = None
for key, value in kwargs.items(): setattr(self, key, value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
Section titled “
Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)
”A waveform created from interpolation of a set of data points.
Parameters:
duration
Section titled “ duration
”int)
–The waveform duration (in ns).
values
Section titled “ values
”ArrayLike)
–Values of the interpolation points. Must be a list of castable to float or a parametrized object.
times
Section titled “ times
”ArrayLike, default:None)
–Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.
interpolator
Section titled “ interpolator
”str, default:'PchipInterpolator')
–The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = "PchipInterpolator", **interpolator_kwargs: Any,): """Initializes a new Interpolated waveform.""" super().__init__(duration) self._values = np.array(values, dtype=float) if times: # fractional times in [0,1] if any([(ft < 0) or (ft > 1) for ft in times]): raise ValueError("All values in `times` must be in [0,1].") self._times = np.array(times, dtype=float) if len(times) != len(self._values): raise ValueError( "Arguments `values` and `times` must be arrays of the same length." ) else: self._times = np.linspace(0, 1, num=len(self._values))
if interpolator not in self._valid_interpolators: raise ValueError( f"Invalid interpolator '{interpolator}', only " "accepts: " + ", ".join(self._valid_interpolators) ) self._interpolator = interpolator self._interpolator_kwargs = interpolator_kwargs
interp_cls = getattr(interpolate, interpolator) self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
PiecewiseLinear(durations: list | tuple, values: list | tuple)
Section titled “
PiecewiseLinear(durations: list | tuple, values: list | tuple)
”A piecewise linear waveform.
Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.
Parameters:
durations
Section titled “ durations
”list | tuple)
–list or tuple of N duration values.
values
Section titled “ values
”list | tuple)
–list or tuple of N+1 waveform values.
Methods:
-
function–Identifies the right waveform in the composition and evaluates it at time t.
-
max–Get the maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
durations(list[float]) –Returns the list of durations of each individual waveform.
-
n_waveforms(int) –Returns the number of waveforms.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
-
times(list[float]) –Returns the list of times when each individual waveform starts.
-
waveforms(list[Waveform]) –Returns a list of the individual waveforms.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, durations: list | tuple, values: list | tuple,) -> None: if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))): raise TypeError( "A PiecewiseLinear waveform requires a list or tuple of durations and values." )
if len(durations) + 1 != len(values) or len(durations) == 1: raise ValueError( "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2." )
for duration in durations: if duration == 0.0: raise ValueError("A PiecewiseLinear interval cannot have zero duration.")
self.values = values
wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]
super().__init__(*wfs)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
durations: list[float]
property
Section titled “
durations: list[float]
property
”Returns the list of durations of each individual waveform.
n_waveforms: int
property
Section titled “
n_waveforms: int
property
”Returns the number of waveforms.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
times: list[float]
property
Section titled “
times: list[float]
property
”Returns the list of times when each individual waveform starts.
waveforms: list[Waveform]
property
Section titled “
waveforms: list[Waveform]
property
”Returns a list of the individual waveforms.
function(t: float) -> float
Section titled “
function(t: float) -> float
”Identifies the right waveform in the composition and evaluates it at time t.
Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float: """Identifies the right waveform in the composition and evaluates it at time t.""" idx = np.searchsorted(self.times, t, side="right") - 1 if idx == -1: return 0.0 if idx == self.n_waveforms: if t == self.times[-1]: idx = idx - 1 else: return 0.0
local_t = t - self.times[idx] value: float = self.waveforms[idx](local_t) return value
max() -> float
Section titled “
max() -> float
”Get the maximum value of the waveform.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the maximum value of the waveform.""" return max([wf.max() for wf in self.waveforms])
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)
Ramp(duration: float, initial_value: float, final_value: float)
Section titled “
Ramp(duration: float, initial_value: float, final_value: float)
”A ramp that linearly interpolates between an initial and final value.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
initial_value
Section titled “ initial_value
”float)
–the initial value at t = 0.
final_value
Section titled “ final_value
”float)
–the final value at t = duration.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, initial_value: float, final_value: float,) -> None: super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
Section titled “
Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)
”An arbitrary sine over a given duration.
Parameters:
duration
Section titled “ duration
”float)
–the total duration.
amplitude
Section titled “ amplitude
”float, default:1.0)
–the amplitude of the sine wave.
omega
Section titled “ omega
”float, default:1.0)
–the frequency of the sine wave.
float, default:0.0)
–the phase of the sine wave.
shift
Section titled “ shift
”float, default:0.0)
–the vertical shift of the sine wave.
Methods:
-
max–Get the approximate maximum value of the waveform.
-
min–Get the approximate minimum value of the waveform.
Attributes:
-
duration(float) –Returns the duration of the waveform.
-
params(dict[str, float | ndarray]) –Dictionary of parameters used by the waveform.
Source code in qoolqit/waveforms/waveforms.py
def __init__( self, duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0,) -> None: super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float
property
Section titled “
duration: float
property
”Returns the duration of the waveform.
params: dict[str, float | np.ndarray]
property
Section titled “
params: dict[str, float | np.ndarray]
property
”Dictionary of parameters used by the waveform.
max() -> float
Section titled “
max() -> float
”Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float: """Get the approximate maximum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._max is None: self._approximate_min_max() return cast(float, self._max)
min() -> float
Section titled “
min() -> float
”Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.
Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float: """Get the approximate minimum value of the waveform.
This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method. """ if self._min is None: self._approximate_min_max() return cast(float, self._min)