Skip to content

Results are limited to the current section: Qoolqit

Product news

qoolqit.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 = [])

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

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.

Requires all nodes to have coordinates.

has_edge_weights: bool

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool

Check if the graph has edges.

has_node_weights: bool

Check if the graph has node weights.

Requires all nodes to have a weight.

sorted_edges: set

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

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

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

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

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

  • **kwargs (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
"""Draw the graph.
Uses the draw_networkx function from NetworkX.
Args:
ax: Axes object to draw on. If None, uses the current Axes.
**kwargs: keyword-arguments to pass to draw_networkx.
"""
if self.has_coords:
if "hide_ticks" not in kwargs:
kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None:
ax = plt.gca()
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox
ylim = ax.get_ylim()
if (ylim[1] - ylim[0]) < 2:
y_center = (ylim[0] + ylim[1]) / 2
ax.set_ylim(y_center - 1, y_center + 1)
plt.tight_layout()
else:
nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">BaseGraph

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
"""Construct a base graph from a set of coordinates.
Arguments:
coords: list or dictionary of coordinate pairs.
"""
if isinstance(coords, list):
nodes = list(range(len(coords)))
coords_dict = {i: pos for i, pos in enumerate(coords)}
elif isinstance(coords, dict):
nodes = list(coords.keys())
coords_dict = coords
graph = cls.from_nodes(nodes)
graph._coords = coords_dict
graph._reset_dicts()
return graph
from_nodes(nodes: Iterable) -> BaseGraph (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">BaseGraph

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
"""Construct a base graph from a set of nodes.
Arguments:
nodes: set of nodes.
"""
graph = cls()
graph.add_nodes_from(nodes)
graph._coords = {i: None for i in graph.nodes}
graph._reset_dicts()
return graph
from_nx(g: Graph) -> BaseGraph (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">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

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
"""Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes:
pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
weight: represents the node weight. Must be a real number.
Edge attributes:
weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes:
- _node_weights : dict[node, float or None]
- _edge_weights : dict[(u,v), float or None]
- _coords : dict[node, (float,float) or None]
"""
if not isinstance(g, nx.Graph):
raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g)
num_nodes = len(g.nodes)
num_edges = len(g.edges)
# validate node attributes
for name, data in g.nodes.data():
unexpected_keys = set(data) - {"weight", "pos"}
if unexpected_keys:
raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos")
if node_pos:
if len(node_pos) != num_nodes:
raise ValueError("Node attribute `pos` must be defined for all nodes")
for name, pos in node_pos.items():
is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
is_real = all(isinstance(p, (float, int)) for p in pos)
if not (is_2D & is_real):
raise TypeError(
f"In node {name} the `pos` attribute must be a 2D tuple/list"
f" of real numbers, got {pos} instead."
)
node_weights = nx.get_node_attributes(g, "weight")
if node_weights:
if len(node_weights) != num_nodes:
raise ValueError("Node attribute `weight` must be defined for all nodes")
for name, weight in node_weights.items():
if not isinstance(weight, (float, int)):
raise TypeError(
f"In node {name} the `weight` attribute must be a real number, "
f"got {type(weight)} instead."
""
)
# validate edge attributes
for u, v, data in g.edges.data():
unexpected_keys = set(data) - {"weight"}
if unexpected_keys:
raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
edge_weights = nx.get_edge_attributes(g, "weight")
if edge_weights:
if len(edge_weights) != num_edges:
raise ValueError("Edge attribute `weight` must be defined for all edges")
for name, weight in edge_weights.items():
if not isinstance(weight, (float, int)):
raise TypeError(
f"In edge {name}, the attribute `weight` must be a real number, "
f"got {type(weight)} instead."
)
# build the instance of the graph
graph = cls()
graph.add_nodes_from(g.nodes)
graph.add_edges_from(g.edges)
graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
graph._coords = nx.get_node_attributes(g, "pos", default=None)
graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
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

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

Returns the maximum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
"""Returns the maximum distance in the graph.
Arguments:
connected: if True/False, computes only over connected/disconnected nodes.
"""
distance: float
if connected is None:
distance = max(self.distances(self.all_node_pairs).values())
elif connected:
distance = max(self.distances(self.sorted_edges).values())
else:
distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
"""Returns the minimum distance in the graph.
Arguments:
connected: if True/False, computes only over connected/disconnected nodes.
"""
distance: float
if connected is None:
distance = min(self.distances(self.all_node_pairs).values())
elif connected:
distance = min(self.distances(self.sorted_edges).values())
else:
distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
return distance
rescale_coords(
*args: Any,
scaling: float | None = None,
spacing: float | None = None,
) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

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

    value to scale by.

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

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
self,
*args: Any,
scaling: float | None = None,
spacing: float | None = None,
) -> None:
"""Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments:
scaling: value to scale by.
spacing: value to set as the minimum distance in the graph.
"""
if self.has_coords:
msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
if (len(args) > 0) or (scaling is None and spacing is None):
raise TypeError(msg)
if scaling is None and spacing is not None:
self._coords = space_coords(self._coords, spacing)
elif spacing is None and scaling is not None:
self._coords = scale_coords(self._coords, scaling)
else:
raise TypeError(msg)
else:
raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None

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

Parameters:

  • radius (float) –

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

Source code in qoolqit/graphs/base_graph.py
def set_ud_edges(self, radius: float) -> None:
"""Reset the set of edges to be equal to the set of unit-disk edges.
Arguments:
radius: the radius to use in determining the set of unit-disk edges.
"""
self.remove_edges_from(list(self.edges))
self.add_edges_from(self.ud_edges(radius))
ud_edges(radius: float) -> set

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

Parameters:

  • radius (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
"""Returns the set of edges given by the intersection of circles of a given radius.
Arguments:
radius: the value
"""
if self.has_coords:
return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
else:
raise AttributeError("Getting unit disk edges is not valid without coordinates.")
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 = [])

The main graph structure to represent problem data.

Parameters:

  • 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

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.

Requires all nodes to have coordinates.

has_edge_weights: bool

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool

Check if the graph has edges.

has_node_weights: bool

Check if the graph has node weights.

Requires all nodes to have a weight.

node_weights: dict

Return the dictionary of node weights.

sorted_edges: set

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 (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

Constructs a circle graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

  • center (tuple, default: (0.0, 0.0) ) –

    point (x, y) to set as the center of the graph.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def circle(
cls,
n: int,
spacing: float = 1.0,
center: tuple = (0.0, 0.0),
) -> DataGraph:
"""Constructs a circle graph, with the respective coordinates.
Arguments:
n: number of nodes.
spacing: distance between each node.
center: point (x, y) to set as the center of the graph.
"""
d_theta = (2.0 * np.pi) / n
r = spacing / (2.0 * np.sin(np.pi / n))
theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n)
coords = [
(x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta))
]
edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)]
graph = cls.from_coordinates(coords)
graph.add_edges_from(edges)
graph._reset_dicts()
return graph
distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
"""Returns a dictionary of distances for a given set of edges.
Distances are calculated directly from the coordinates. Raises an error
if there are no coordinates on the graph.
Arguments:
edge_list: set of edges.
"""
if self.has_coords:
if edge_list is None:
edge_list = self.all_node_pairs
elif len(edge_list) == 0: # type: ignore [arg-type]
raise ValueError("Trying to compute distances for an empty edge list.")
return distances(self.coords, edge_list)
else:
raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

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

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

  • **kwargs (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
"""Draw the graph.
Uses the draw_networkx function from NetworkX.
Args:
ax: Axes object to draw on. If None, uses the current Axes.
**kwargs: keyword-arguments to pass to draw_networkx.
"""
if self.has_coords:
if "hide_ticks" not in kwargs:
kwargs["hide_ticks"] = False
nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)
if ax is None:
ax = plt.gca()
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
# minimum ybox
ylim = ax.get_ylim()
if (ylim[1] - ylim[0]) < 2:
y_center = (ylim[0] + ylim[1]) / 2
ax.set_ylim(y_center - 1, y_center + 1)
plt.tight_layout()
else:
nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">BaseGraph

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
"""Construct a base graph from a set of coordinates.
Arguments:
coords: list or dictionary of coordinate pairs.
"""
if isinstance(coords, list):
nodes = list(range(len(coords)))
coords_dict = {i: pos for i, pos in enumerate(coords)}
elif isinstance(coords, dict):
nodes = list(coords.keys())
coords_dict = coords
graph = cls.from_nodes(nodes)
graph._coords = coords_dict
graph._reset_dicts()
return graph
from_matrix(data: ndarray) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">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.

Parameters:

  • data (ndarray) –

    real symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_matrix(cls, data: np.ndarray) -> 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: real symmetric square matrix.
"""
if data.ndim != 2:
raise ValueError("2D Matrix required.")
if not np.allclose(data, data.T, rtol=0.0, atol=1e-7):
raise ValueError("Matrix must be symmetric.")
# Absolute values below this tolerance are treated as zeros.
# The corresponding node or edge weight is neglected (weight = None).
nonzero_tol = 1e-7
diag = np.diag(data)
n_nodes = len(diag)
if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=nonzero_tol):
node_weights = {i: None for i in range(n_nodes)}
else:
node_weights = {i: diag[i].item() for i in range(n_nodes)}
edge_list = [
(i, j)
for i in range(n_nodes)
for j in range(i + 1, n_nodes)
if (np.abs(data[i, j]) >= nonzero_tol)
]
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 (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">BaseGraph

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
"""Construct a base graph from a set of nodes.
Arguments:
nodes: set of nodes.
"""
graph = cls()
graph.add_nodes_from(nodes)
graph._coords = {i: None for i in graph.nodes}
graph._reset_dicts()
return graph
from_nx(g: Graph) -> BaseGraph (qoolqit.graphs.base_graph.BaseGraph)" href="#qoolqit.graphs.BaseGraph">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

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
"""Convert a NetworkX Graph object into a QoolQit graph instance.
The input `networkx.Graph` graph must be defined only with the following allowed
Node attributes:
pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
weight: represents the node weight. Must be a real number.
Edge attributes:
weight: represents the edge weight. Must be a real number.
Returns an instance of the class with following attributes:
- _node_weights : dict[node, float or None]
- _edge_weights : dict[(u,v), float or None]
- _coords : dict[node, (float,float) or None]
"""
if not isinstance(g, nx.Graph):
raise TypeError("Input must be a networkx.Graph instance.")
g = nx.convert_node_labels_to_integers(g)
num_nodes = len(g.nodes)
num_edges = len(g.edges)
# validate node attributes
for name, data in g.nodes.data():
unexpected_keys = set(data) - {"weight", "pos"}
if unexpected_keys:
raise ValueError(f"{unexpected_keys} not allowed in node attributes.")
node_pos = nx.get_node_attributes(g, "pos")
if node_pos:
if len(node_pos) != num_nodes:
raise ValueError("Node attribute `pos` must be defined for all nodes")
for name, pos in node_pos.items():
is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
is_real = all(isinstance(p, (float, int)) for p in pos)
if not (is_2D & is_real):
raise TypeError(
f"In node {name} the `pos` attribute must be a 2D tuple/list"
f" of real numbers, got {pos} instead."
)
node_weights = nx.get_node_attributes(g, "weight")
if node_weights:
if len(node_weights) != num_nodes:
raise ValueError("Node attribute `weight` must be defined for all nodes")
for name, weight in node_weights.items():
if not isinstance(weight, (float, int)):
raise TypeError(
f"In node {name} the `weight` attribute must be a real number, "
f"got {type(weight)} instead."
""
)
# validate edge attributes
for u, v, data in g.edges.data():
unexpected_keys = set(data) - {"weight"}
if unexpected_keys:
raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
edge_weights = nx.get_edge_attributes(g, "weight")
if edge_weights:
if len(edge_weights) != num_edges:
raise ValueError("Edge attribute `weight` must be defined for all edges")
for name, weight in edge_weights.items():
if not isinstance(weight, (float, int)):
raise TypeError(
f"In edge {name}, the attribute `weight` must be a real number, "
f"got {type(weight)} instead."
)
# build the instance of the graph
graph = cls()
graph.add_nodes_from(g.nodes)
graph.add_edges_from(g.edges)
graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
graph._coords = nx.get_node_attributes(g, "pos", default=None)
graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)
return graph
from_pyg(
data: 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 (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">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.

Parameters:

  • data (Data) –

    PyTorch Geometric Data object to convert.

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

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

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

    extra edge attributes to copy (beyond edge_attr).

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

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

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

    Data attribute to use as node weights.

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

    Data attribute to use as edge weights.

Returns:

  • DataGraph

    DataGraph with _coords, _node_weights, _edge_weights

  • DataGraph

    populated where applicable.

Raises:

  • ImportError

    if torch_geometric is not installed.

  • TypeError

    if data is not a torch_geometric.data.Data instance, or if a weight attribute is not a torch.Tensor.

  • AttributeError

    if a specified weight attribute is missing.

  • ValueError

    if a weight tensor has an incompatible shape or size.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_pyg(
cls,
data: torch_geometric.data.Data,
node_attrs: Iterable[str] | None = None,
edge_attrs: Iterable[str] | None = None,
graph_attrs: Iterable[str] | None = None,
node_weights_attr: str | None = None,
edge_weights_attr: str | None = None,
) -> DataGraph:
"""Convert a PyTorch Geometric Data object into a DataGraph instance.
Requires ``torch_geometric``. Uses ``to_networkx`` internally.
**Default attributes copied (if present on** ``data`` **):**
- Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``)
- Edge: ``edge_attr``
- Graph: ``y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit weights** (``_node_weights``, ``_edge_weights``) are not
populated automatically — use the explicit parameters:
- ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or
``(N, 1)``. Defaults to ``None``.
- ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or
``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count).
Defaults to ``None``.
The weight attribute is also stored as a regular node/edge attribute.
Arguments:
data: PyTorch Geometric Data object to convert.
node_attrs: extra node attributes to copy (beyond x and pos).
edge_attrs: extra edge attributes to copy (beyond edge_attr).
graph_attrs: extra graph-level attributes to copy (beyond y).
node_weights_attr: Data attribute to use as node weights.
edge_weights_attr: Data attribute to use as edge weights.
Returns:
DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights``
populated where applicable.
Raises:
ImportError: if ``torch_geometric`` is not installed.
TypeError: if ``data`` is not a ``torch_geometric.data.Data``
instance, or if a weight attribute is not a ``torch.Tensor``.
AttributeError: if a specified weight attribute is missing.
ValueError: if a weight tensor has an incompatible shape or size.
"""
try:
from torch_geometric.data import Data
from torch_geometric.utils import to_networkx
except ImportError as e:
raise ImportError("Please, install the `torch_geometric` package.") from e
if not isinstance(data, Data):
raise TypeError("Input must be a torch_geometric.data.Data object.")
# Validate weight attrs early and keep the squeezed tensors
node_tensor = (
cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node")
if node_weights_attr is not None
else None
)
edge_tensor = (
cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge")
if edge_weights_attr is not None
else None
)
# Select unique attributes and add default ones only if present in the data
node_attrs_set = {k for k in {"x", "pos"} if k in data}
if node_attrs is not None:
node_attrs_set |= set(node_attrs)
if node_weights_attr is not None:
node_attrs_set.add(node_weights_attr)
edge_attrs_set = {k for k in {"edge_attr"} if k in data}
if edge_attrs is not None:
edge_attrs_set |= set(edge_attrs)
if edge_weights_attr is not None:
edge_attrs_set.add(edge_weights_attr)
graph_attrs_set = {k for k in {"y"} if k in data}
if graph_attrs is not None:
graph_attrs_set |= set(graph_attrs)
# Convert to NetworkX (undirected, no self-loops)
nx_graph = to_networkx(
data,
node_attrs=list(node_attrs_set),
edge_attrs=list(edge_attrs_set),
graph_attrs=list(graph_attrs_set),
to_undirected=True,
remove_self_loops=True,
)
# Build the DataGraph: edges carry their data, nodes carry their data
graph = cls(nx_graph.edges(data=True))
graph.add_nodes_from(nx_graph.nodes(data=True))
graph.graph = nx_graph.graph
# Re-initialize QoolQit internal dicts for all nodes/edges
graph._coords = {n: None for n in graph.nodes}
graph._reset_dicts()
# pos → _coords (stored as list [x, y] by to_networkx)
for node, node_data in nx_graph.nodes(data=True):
if "pos" in node_data:
graph._coords[node] = tuple(node_data["pos"]) # type: ignore[assignment]
# node_weights_attr → _node_weights
if node_tensor is not None:
for i in range(data.num_nodes):
graph._node_weights[i] = node_tensor[i].item()
# edge_weights_attr → _edge_weights
if edge_tensor is not None:
seen: set = set()
for idx in range(data.edge_index.shape[1]):
u = int(data.edge_index[0, idx].item())
v = int(data.edge_index[1, idx].item())
key = (min(u, v), max(u, v))
if key not in seen:
graph._edge_weights[key] = edge_tensor[idx].item()
seen.add(key)
return graph
heavy_hexagonal(
m: int, n: int, spacing: float = 1.0
) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

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

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Notes
Source code in qoolqit/graphs/data_graph.py
@classmethod
def heavy_hexagonal(
cls,
m: int,
n: int,
spacing: float = 1.0,
) -> DataGraph:
"""
Constructs a heavy-hexagonal lattice graph, with respective coordinates.
Arguments:
m: Number of rows of hexagons.
n: Number of columns of hexagons.
spacing: The distance between adjacent nodes on the final lattice.
Notes:
The heavy-hexagonal lattice is a regular hexagonal lattice where
each edge is decorated with an additional lattice site.
"""
G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True)
pos_unit = nx.get_node_attributes(G_hex, "pos")
G_heavy = nx.Graph()
scaling_factor = 2 * spacing
label_map = {}
for old_label, (x, y) in pos_unit.items():
# Relabel to an even-integer grid to make space for midpoint nodes
new_label = (2 * old_label[0], 2 * old_label[1])
label_map[old_label] = new_label
# Scale positions and add the node to the new graph
new_pos = (x * scaling_factor, y * scaling_factor)
G_heavy.add_node(new_label, pos=new_pos)
for u_old, v_old in G_hex.edges():
u_new, v_new = label_map[u_old], label_map[v_old]
mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)
pos_u = G_heavy.nodes[u_new]["pos"]
pos_v = G_heavy.nodes[v_new]["pos"]
mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)
G_heavy.add_node(mid_label, pos=mid_pos)
G_heavy.add_edge(u_new, mid_label)
G_heavy.add_edge(mid_label, v_new)
final_nodes = sorted(list(G_heavy.nodes()))
final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]
label_to_int = {label: i for i, label in enumerate(final_nodes)}
final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]
graph = cls.from_coordinates(final_coords)
graph.add_edges_from(final_edges)
graph._reset_dicts()
return graph
hexagonal(
m: int, n: int, spacing: float = 1.0
) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def hexagonal(
cls,
m: int,
n: int,
spacing: float = 1.0,
) -> DataGraph:
"""
Constructs a hexagonal lattice graph, with respective coordinates.
Arguments:
m: Number of rows of hexagons.
n: Number of columns of hexagons.
spacing: The distance between adjacent nodes on the final lattice.
"""
G = nx.hexagonal_lattice_graph(m, n, with_positions=True)
G = nx.convert_node_labels_to_integers(G)
pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos)
graph.add_edges_from(G.edges)
graph._reset_dicts()
return graph
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

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 (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

Constructs a line graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def line(cls, n: int, spacing: float = 1.0) -> DataGraph:
"""Constructs a line graph, with the respective coordinates.
Arguments:
n: number of nodes.
spacing: distance between each node.
"""
coords = [(i * spacing, 0.0) for i in range(n)]
graph = cls.from_coordinates(coords)
edges = [(i, i + 1) for i in range(0, n - 1)]
graph.add_edges_from(edges)
graph._reset_dicts()
return graph
max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
"""Returns the maximum distance in the graph.
Arguments:
connected: if True/False, computes only over connected/disconnected nodes.
"""
distance: float
if connected is None:
distance = max(self.distances(self.all_node_pairs).values())
elif connected:
distance = max(self.distances(self.sorted_edges).values())
else:
distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
"""Returns the minimum distance in the graph.
Arguments:
connected: if True/False, computes only over connected/disconnected nodes.
"""
distance: float
if connected is None:
distance = min(self.distances(self.all_node_pairs).values())
elif connected:
distance = min(self.distances(self.sorted_edges).values())
else:
distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
return distance
random_er(
n: int, p: float, seed: int | None = None
) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

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

Parameters:

  • n (int) –

    number of nodes.

  • p (float) –

    probability that any two nodes connect.

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

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph:
"""Constructs an Erdős–Rényi random graph.
Arguments:
n: number of nodes.
p: probability that any two nodes connect.
seed: random seed.
"""
base_graph = nx.erdos_renyi_graph(n, p, seed)
graph = DataGraph.from_nodes(list(base_graph.nodes))
graph.add_edges_from(base_graph.edges)
graph._reset_dicts()
return graph
random_ud(
n: int, radius: float = 1.0, L: float | None = None
) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">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).

Parameters:

  • n (int) –

    number of nodes.

  • radius (float, default: 1.0 ) –

    radius to use for defining the unit-disk edges.

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

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

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_ud(
cls,
n: int,
radius: float = 1.0,
L: float | None = None,
) -> DataGraph:
"""Constructs a random unit-disk graph.
The nodes are sampled uniformly from a square of size (L x L).
If L is not given, it is estimated based on a rough heuristic that
of packing N nodes on a square of side L such that the expected
minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).
Arguments:
n: number of nodes.
radius: radius to use for defining the unit-disk edges.
L: size of the square on which to sample the node coordinates.
"""
if L is None:
L = (radius / 2) * ((np.pi * n) ** 0.5)
coords = random_coords(n, L)
graph = cls.from_coordinates(coords)
edges = graph.ud_edges(radius)
graph.add_edges_from(edges)
graph._reset_dicts()
return graph
rescale_coords(
*args: Any,
scaling: float | None = None,
spacing: float | None = None,
) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

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

    value to scale by.

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

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
self,
*args: Any,
scaling: float | None = None,
spacing: float | None = None,
) -> None:
"""Rescales the node coordinates by a factor.
Accepts either a scaling or a spacing factor.
Arguments:
scaling: value to scale by.
spacing: value to set as the minimum distance in the graph.
"""
if self.has_coords:
msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
if (len(args) > 0) or (scaling is None and spacing is None):
raise TypeError(msg)
if scaling is None and spacing is not None:
self._coords = space_coords(self._coords, spacing)
elif spacing is None and scaling is not None:
self._coords = scale_coords(self._coords, scaling)
else:
raise TypeError(msg)
else:
raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
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 (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of square.

  • n (int) –

    Number of columns of square.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def square(
cls,
m: int,
n: int,
spacing: float = 1.0,
) -> DataGraph:
"""
Constructs a square lattice graph, with respective coordinates.
Arguments:
m: Number of rows of square.
n: Number of columns of square.
spacing: The distance between adjacent nodes on the final lattice.
"""
G = nx.grid_2d_graph(m, n)
final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)]
G = nx.convert_node_labels_to_integers(G)
graph = DataGraph.from_coordinates(final_coords)
graph.add_edges_from(G.edges)
graph._reset_dicts()
return graph
to_pyg(
node_attrs: Iterable[str] | None = None,
edge_attrs: Iterable[str] | None = None,
graph_attrs: Iterable[str] | None = None,
node_weights_attr: str = "weight",
edge_weights_attr: str = "edge_weight",
) -> Data

Convert the DataGraph to a PyTorch Geometric Data object.

Requires torch_geometric. Uses from_networkx internally.

Default attributes exported (if present on the graph):

  • Node "x"data.x; Edge "edge_attr"data.edge_attr
  • Graph "y"data.y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit internal dicts exported when populated:

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

Parameters:

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

    extra node attributes to export (beyond x).

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

    extra edge attributes to export (beyond edge_attr).

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

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

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

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

  • edge_weights_attr (str, default: 'edge_weight' ) –

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

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is not installed.

Source code in qoolqit/graphs/data_graph.py
def to_pyg(
self,
node_attrs: Iterable[str] | None = None,
edge_attrs: Iterable[str] | None = None,
graph_attrs: Iterable[str] | None = None,
node_weights_attr: str = "weight",
edge_weights_attr: str = "edge_weight",
) -> torch_geometric.data.Data:
"""Convert the DataGraph to a PyTorch Geometric Data object.
Requires ``torch_geometric``. Uses ``from_networkx`` internally.
**Default attributes exported (if present on the graph):**
- Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr``
- Graph ``"y"`` → ``data.y``
Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.
**QoolQit internal dicts exported when populated:**
- ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``)
- ``_node_weights`` → ``data.`` (float64, shape
``(N,)``). Defaults to ``"weight"``.
- ``_edge_weights`` → ``data.`` (float64, shape
``(2*E,)``). Defaults to ``"edge_weight"``.
Arguments:
node_attrs: extra node attributes to export (beyond x).
edge_attrs: extra edge attributes to export (beyond edge_attr).
graph_attrs: extra graph-level attributes to export (beyond y).
node_weights_attr: Data attribute name for node weights.
Defaults to ``"weight"``.
edge_weights_attr: Data attribute name for edge weights.
Defaults to ``"edge_weight"``.
Returns:
PyTorch Geometric Data object.
Raises:
ImportError: if ``torch_geometric`` is not installed.
"""
try:
import torch
from torch_geometric.utils import from_networkx
except ImportError as e:
raise ImportError("Please, install the `torch_geometric` package.") from e
node_attrs_set = set(node_attrs) if node_attrs else set()
edge_attrs_set = set(edge_attrs) if edge_attrs else set()
graph_attrs_list = list(graph_attrs) if graph_attrs else []
# Add default PyG attributes if present in the graph
if any("x" in d for _, d in self.nodes(data=True)):
node_attrs_set.add("x")
if any("edge_attr" in d for _, _, d in self.edges(data=True)):
edge_attrs_set.add("edge_attr")
if "y" in self.graph:
graph_attrs_list.append("y")
# Build a filtered copy with only the requested attributes
filtered_graph = nx.Graph()
filtered_graph.add_nodes_from(self.nodes())
filtered_graph.add_edges_from(self.edges())
for node, node_data in self.nodes(data=True):
for key, value in node_data.items():
if key in node_attrs_set:
filtered_graph.nodes[node][key] = value
for u, v, edge_data in self.edges(data=True):
for key, value in edge_data.items():
if key in edge_attrs_set:
filtered_graph.edges[u, v][key] = value
for attr in graph_attrs_list:
if attr in self.graph:
filtered_graph.graph[attr] = self.graph[attr]
data = from_networkx(filtered_graph)
# Export _coords → pos
if self.has_coords:
positions = [self._coords[n] for n in sorted(self.nodes())]
data.pos = torch.tensor(positions, dtype=torch.float64)
# Export _node_weights → node_weights_attr
if self.has_node_weights:
weights = [self._node_weights[n] for n in sorted(self.nodes())]
setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))
# Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index)
if self.has_edge_weights:
edge_weights: list[float] = []
for i in range(data.edge_index.shape[1]):
u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item())
edge_key = (min(u, v), max(u, v))
edge_weights.append(float(self._edge_weights[edge_key])) # type: ignore[arg-type]
setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))
return data
triangular(
m: int, n: int, spacing: float = 1.0
) -> DataGraph (qoolqit.graphs.data_graph.DataGraph)" href="#qoolqit.graphs.DataGraph">DataGraph

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of triangles.

  • n (int) –

    Number of columns of triangles.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def triangular(
cls,
m: int,
n: int,
spacing: float = 1.0,
) -> DataGraph:
"""
Constructs a triangular lattice graph, with respective coordinates.
Arguments:
m: Number of rows of triangles.
n: Number of columns of triangles.
spacing: The distance between adjacent nodes on the final lattice.
"""
G = nx.triangular_lattice_graph(m, n, with_positions=True)
G = nx.convert_node_labels_to_integers(G)
pos_unit = nx.get_node_attributes(G, "pos")
final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}
graph = cls.from_coordinates(final_pos)
graph.add_edges_from(G.edges)
graph._reset_dicts()
return graph
ud_edges(radius: float) -> set

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

Parameters:

  • radius (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
"""Returns the set of edges given by the intersection of circles of a given radius.
Arguments:
radius: the value
"""
if self.has_coords:
return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
else:
raise AttributeError("Getting unit disk edges is not valid without coordinates.")
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

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

Parameters:

  • 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

Return a dictionary of edge distances.

Parameters:

  • coords (dict) –

    dictionary of node coordinates.

  • 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

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

Parameters:

  • n (int) –

    number of coordinate pairs to generate.

  • L (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

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

Scale the coordinates by a given value.

Parameters:

  • coords (dict) –

    dictionary of node coordinates.

  • 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

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

Parameters:

  • coords (dict) –

    dictionary of node coordinates.

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