Skip to content

qoolqit.graphs

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 property

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

coords: dict property writable

Return the dictionary of node coordinates.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

sorted_edges: set property

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 classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords
    (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph

from_nodes(nodes: Iterable) -> BaseGraph classmethod

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

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

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

coords: dict property writable

Return the dictionary of node coordinates.

edge_weights: dict property writable

Return the dictionary of edge weights.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

node_weights: dict property writable

Return the dictionary of node weights.

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

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 classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords
    (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph

from_matrix(data: ArrayLike) -> DataGraph classmethod

Constructs a graph from a symmetric square matrix.

The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.

Parameters:

  • data
    (ArrayLike) –

    symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_matrix(cls, data: ArrayLike) -> DataGraph:
    """Constructs a graph from a symmetric square matrix.

    The diagonal values are set as the node weights. For each entry (i, j)
    where M[i, j] != 0 an edge (i, j) is added to the graph and the value
    M[i, j] is set as its weight.

    Arguments:
        data: symmetric square matrix.
    """
    if data.ndim != 2:
        raise ValueError("2D Matrix required.")
    if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32):
        raise ValueError("Matrix must be symmetric.")

    diag = np.diag(data)
    n_nodes = len(diag)
    node_weights = {i: diag[i] for i in range(n_nodes)}
    if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32):
        node_weights = {i: None for i in range(n_nodes)}
    else:
        node_weights = {i: diag[i].item() for i in range(n_nodes)}

    data[data <= ATOL_32] = 0.0
    non_zero = data.nonzero()
    i_list = non_zero[0].tolist()
    j_list = non_zero[1].tolist()

    edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j]
    edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}

    graph = cls.from_nodes(range(n_nodes))
    graph.add_edges_from(edge_list)
    graph.node_weights = node_weights
    graph.edge_weights = edge_weights
    return graph

from_nodes(nodes: Iterable) -> BaseGraph classmethod

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

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]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph

from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph classmethod

Convert a PyTorch Geometric Data object into a DataGraph instance.

Requires torch_geometric. Uses to_networkx internally.

Default attributes copied (if present on data ):

  • Node: x, pos (pos is also stored in _coords)
  • Edge: edge_attr
  • Graph: y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit weights (_node_weights, _edge_weights) are not populated automatically — use the explicit parameters:

  • node_weights_attr: real-valued tensor of shape (N,) or (N, 1). Defaults to None.
  • edge_weights_attr: real-valued tensor of shape (E,) or (E, 1) where E = edge_index.shape[1] (directed count). Defaults to None.

The weight attribute is also stored as a regular node/edge attribute.

Parameters:

  • data
    (Data) –

    PyTorch Geometric Data object to convert.

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

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

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

    extra edge attributes to copy (beyond edge_attr).

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

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

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

    Data attribute to use as node weights.

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

    Data attribute to use as edge weights.

Returns:

  • DataGraph

    DataGraph with _coords, _node_weights, _edge_weights

  • DataGraph

    populated where applicable.

Raises:

  • ImportError

    if torch_geometric is not installed.

  • TypeError

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

  • AttributeError

    if a specified weight attribute is missing.

  • ValueError

    if a weight tensor has an incompatible shape or size.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_pyg(
    cls,
    data: torch_geometric.data.Data,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str | None = None,
    edge_weights_attr: str | None = None,
) -> DataGraph:
    """Convert a PyTorch Geometric Data object into a DataGraph instance.

    Requires ``torch_geometric``. Uses ``to_networkx`` internally.

    **Default attributes copied (if present on** ``data`` **):**

    - Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``)
    - Edge: ``edge_attr``
    - Graph: ``y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit weights** (``_node_weights``, ``_edge_weights``) are not
    populated automatically — use the explicit parameters:

    - ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or
      ``(N, 1)``. Defaults to ``None``.
    - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or
      ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count).
      Defaults to ``None``.

    The weight attribute is also stored as a regular node/edge attribute.

    Arguments:
        data: PyTorch Geometric Data object to convert.
        node_attrs: extra node attributes to copy (beyond x and pos).
        edge_attrs: extra edge attributes to copy (beyond edge_attr).
        graph_attrs: extra graph-level attributes to copy (beyond y).
        node_weights_attr: Data attribute to use as node weights.
        edge_weights_attr: Data attribute to use as edge weights.

    Returns:
        DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights``
        populated where applicable.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
        TypeError: if ``data`` is not a ``torch_geometric.data.Data``
            instance, or if a weight attribute is not a ``torch.Tensor``.
        AttributeError: if a specified weight attribute is missing.
        ValueError: if a weight tensor has an incompatible shape or size.
    """
    try:
        from torch_geometric.data import Data
        from torch_geometric.utils import to_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    if not isinstance(data, Data):
        raise TypeError("Input must be a torch_geometric.data.Data object.")

    # Validate weight attrs early and keep the squeezed tensors
    node_tensor = (
        cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node")
        if node_weights_attr is not None
        else None
    )
    edge_tensor = (
        cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge")
        if edge_weights_attr is not None
        else None
    )

    # Select unique attributes and add default ones only if present in the data
    node_attrs_set = {k for k in {"x", "pos"} if k in data}
    if node_attrs is not None:
        node_attrs_set |= set(node_attrs)
    if node_weights_attr is not None:
        node_attrs_set.add(node_weights_attr)

    edge_attrs_set = {k for k in {"edge_attr"} if k in data}
    if edge_attrs is not None:
        edge_attrs_set |= set(edge_attrs)
    if edge_weights_attr is not None:
        edge_attrs_set.add(edge_weights_attr)

    graph_attrs_set = {k for k in {"y"} if k in data}
    if graph_attrs is not None:
        graph_attrs_set |= set(graph_attrs)

    # Convert to NetworkX (undirected, no self-loops)
    nx_graph = to_networkx(
        data,
        node_attrs=list(node_attrs_set),
        edge_attrs=list(edge_attrs_set),
        graph_attrs=list(graph_attrs_set),
        to_undirected=True,
        remove_self_loops=True,
    )

    # Build the DataGraph: edges carry their data, nodes carry their data
    graph = cls(nx_graph.edges(data=True))
    graph.add_nodes_from(nx_graph.nodes(data=True))
    graph.graph = nx_graph.graph

    # Re-initialize QoolQit internal dicts for all nodes/edges
    graph._coords = {n: None for n in graph.nodes}
    graph._reset_dicts()

    # pos → _coords (stored as list [x, y] by to_networkx)
    for node, node_data in nx_graph.nodes(data=True):
        if "pos" in node_data:
            graph._coords[node] = tuple(node_data["pos"])  # type: ignore[assignment]

    # node_weights_attr → _node_weights
    if node_tensor is not None:
        for i in range(data.num_nodes):
            graph._node_weights[i] = node_tensor[i].item()

    # edge_weights_attr → _edge_weights
    if edge_tensor is not None:
        seen: set = set()
        for idx in range(data.edge_index.shape[1]):
            u = int(data.edge_index[0, idx].item())
            v = int(data.edge_index[1, idx].item())
            key = (min(u, v), max(u, v))
            if key not in seen:
                graph._edge_weights[key] = edge_tensor[idx].item()
                seen.add(key)

    return graph

heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

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

The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def heavy_hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.

    Notes:
        The heavy-hexagonal lattice is a regular hexagonal lattice where
        each edge is decorated with an additional lattice site.
    """
    G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    pos_unit = nx.get_node_attributes(G_hex, "pos")

    G_heavy = nx.Graph()
    scaling_factor = 2 * spacing

    label_map = {}
    for old_label, (x, y) in pos_unit.items():
        # Relabel to an even-integer grid to make space for midpoint nodes
        new_label = (2 * old_label[0], 2 * old_label[1])
        label_map[old_label] = new_label

        # Scale positions and add the node to the new graph
        new_pos = (x * scaling_factor, y * scaling_factor)
        G_heavy.add_node(new_label, pos=new_pos)

    for u_old, v_old in G_hex.edges():
        u_new, v_new = label_map[u_old], label_map[v_old]

        mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)

        pos_u = G_heavy.nodes[u_new]["pos"]
        pos_v = G_heavy.nodes[v_new]["pos"]
        mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)

        G_heavy.add_node(mid_label, pos=mid_pos)
        G_heavy.add_edge(u_new, mid_label)
        G_heavy.add_edge(mid_label, v_new)

    final_nodes = sorted(list(G_heavy.nodes()))

    final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]

    label_to_int = {label: i for i, label in enumerate(final_nodes)}

    final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]

    graph = cls.from_coordinates(final_coords)
    graph.add_edges_from(final_edges)
    graph._reset_dicts()
    return graph

hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

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 classmethod

Constructs a line graph, with the respective coordinates.

Parameters:

  • n
    (int) –

    number of nodes.

  • spacing
    (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def line(cls, n: int, spacing: float = 1.0) -> DataGraph:
    """Constructs a line graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
    """
    coords = [(i * spacing, 0.0) for i in range(n)]
    graph = cls.from_coordinates(coords)
    edges = [(i, i + 1) for i in range(0, n - 1)]
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph

max_distance(connected: bool | None = None) -> float

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 classmethod

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

Parameters:

  • n
    (int) –

    number of nodes.

  • p
    (float) –

    probability that any two nodes connect.

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

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph:
    """Constructs an Erdős–Rényi random graph.

    Arguments:
        n: number of nodes.
        p: probability that any two nodes connect.
        seed: random seed.
    """
    base_graph = nx.erdos_renyi_graph(n, p, seed)
    graph = DataGraph.from_nodes(list(base_graph.nodes))
    graph.add_edges_from(base_graph.edges)
    graph._reset_dicts()
    return graph

random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph classmethod

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 classmethod

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of square.

  • n
    (int) –

    Number of columns of square.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def square(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a square lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of square.
        n: Number of columns of square.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.grid_2d_graph(m, n)
    final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)]
    G = nx.convert_node_labels_to_integers(G)

    graph = DataGraph.from_coordinates(final_coords)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph

to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data

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.<node_weights_attr>`` (float64, shape
      ``(N,)``). Defaults to ``"weight"``.
    - ``_edge_weights`` → ``data.<edge_weights_attr>`` (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

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)