From 7efce0795070c7dd8339c8deed04f5d815f617cd Mon Sep 17 00:00:00 2001 From: Stefano Grava Date: Wed, 5 Aug 2026 16:57:04 +0200 Subject: [PATCH 1/4] add interaction matrix --- qoolqit/graphs/base_graph.py | 20 +++++++++++++ tests/test_graphs/test_base_graph.py | 43 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/qoolqit/graphs/base_graph.py b/qoolqit/graphs/base_graph.py index 0c360e896..e4e4ad8d7 100644 --- a/qoolqit/graphs/base_graph.py +++ b/qoolqit/graphs/base_graph.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt import networkx as nx +import numpy as np from matplotlib.axes import Axes from .utils import ( @@ -246,6 +247,25 @@ 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()} + def interaction_matrix(self) -> np.ndarray: + """Rydberg model interaction 1/r^6 between pairs of nodes, as a matrix. + + Node ordering follows `self.nodes` insertion order. + The diagonal is 0, since there is no self-interaction. + + Returns: + Symmetric N x N matrix of dtype float64, where N is the number of nodes. + """ + index = {node: i for i, node in enumerate(self.nodes)} + n_nodes = len(index) + matrix = np.zeros((n_nodes, n_nodes), dtype=np.float64) + + for (u, v), interaction in self.interactions().items(): + i, j = index[u], index[v] + matrix[i, j] = matrix[j, i] = interaction + + return matrix + def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph. diff --git a/tests/test_graphs/test_base_graph.py b/tests/test_graphs/test_base_graph.py index 415911e96..fffd631bb 100644 --- a/tests/test_graphs/test_base_graph.py +++ b/tests/test_graphs/test_base_graph.py @@ -39,6 +39,14 @@ def test_basegraph_init(n_nodes: int) -> None: with pytest.raises(AttributeError): graph.max_distance() + no_coords_match = "Trying to compute distances for a graph without coordinates." + + with pytest.raises(AttributeError, match=no_coords_match): + graph.interactions() + + with pytest.raises(AttributeError, match=no_coords_match): + graph.interaction_matrix() + with pytest.raises(AttributeError): graph.is_ud_graph() @@ -61,6 +69,41 @@ def test_basegraph_init(n_nodes: int) -> None: assert len(graph.ud_edges(radius=10.0 * scale)) == max_n_edges +@pytest.mark.parametrize("n_nodes", [5, 10, 50]) +def test_basegraph_interaction_matrix(n_nodes: int) -> None: + + n_edges = 2 * n_nodes + + edge_list = random_edge_list(range(n_nodes), n_edges) + graph = BaseGraph(edge_list) + + # Because a random edge list might leave one disconnected one + actual_n_nodes = len(graph.nodes) + + no_coords_match = "Trying to compute distances for a graph without coordinates." + + with pytest.raises(AttributeError, match=no_coords_match): + graph.interactions() + + with pytest.raises(AttributeError, match=no_coords_match): + graph.interaction_matrix() + + scale = ((actual_n_nodes**0.5) ** 0.5) / 2 + coords = random_coords(actual_n_nodes, scale) + graph.coords = {i: pos for i, pos in zip(graph.nodes, coords)} + + interaction_matrix = graph.interaction_matrix() + index = {node: i for i, node in enumerate(graph.nodes)} + + assert interaction_matrix.shape == (actual_n_nodes, actual_n_nodes) + assert np.allclose(interaction_matrix, interaction_matrix.T) + assert np.allclose(np.diag(interaction_matrix), 0.0) + + for (u, v), interaction in graph.interactions().items(): + assert np.isclose(interaction_matrix[index[u], index[v]], interaction) + assert np.isclose(interaction_matrix[index[v], index[u]], interaction) + + @pytest.mark.parametrize("n_nodes", [5, 10, 50]) def test_basegraph_constructors(n_nodes: int) -> None: scale = ((n_nodes**0.5) ** 0.5) / 2 From 719a1b9a0010001aec908f8016f375f9ccc79a11 Mon Sep 17 00:00:00 2001 From: Stefano Grava Date: Thu, 6 Aug 2026 09:23:04 +0200 Subject: [PATCH 2/4] refine interaction matrix test --- tests/test_graphs/test_base_graph.py | 46 ++++++++++------------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/tests/test_graphs/test_base_graph.py b/tests/test_graphs/test_base_graph.py index fffd631bb..b4e8a0ed7 100644 --- a/tests/test_graphs/test_base_graph.py +++ b/tests/test_graphs/test_base_graph.py @@ -5,6 +5,7 @@ import networkx as nx import numpy as np import pytest +from scipy.spatial.distance import pdist, squareform from torch_geometric.data import Data from qoolqit.graphs import BaseGraph, random_coords, random_edge_list @@ -69,39 +70,26 @@ def test_basegraph_init(n_nodes: int) -> None: assert len(graph.ud_edges(radius=10.0 * scale)) == max_n_edges -@pytest.mark.parametrize("n_nodes", [5, 10, 50]) -def test_basegraph_interaction_matrix(n_nodes: int) -> None: - - n_edges = 2 * n_nodes +@pytest.mark.parametrize("n_nodes", [3, 8, 13]) +def test_basegraph_interactions(n_nodes: int) -> None: - edge_list = random_edge_list(range(n_nodes), n_edges) - graph = BaseGraph(edge_list) + rng = np.random.default_rng(0) + coords_array = rng.uniform(-1, 1, size=(n_nodes, 2)) + graph = BaseGraph.from_coordinates([c for c in coords_array]) - # Because a random edge list might leave one disconnected one - actual_n_nodes = len(graph.nodes) - - no_coords_match = "Trying to compute distances for a graph without coordinates." - - with pytest.raises(AttributeError, match=no_coords_match): - graph.interactions() - - with pytest.raises(AttributeError, match=no_coords_match): - graph.interaction_matrix() - - scale = ((actual_n_nodes**0.5) ** 0.5) / 2 - coords = random_coords(actual_n_nodes, scale) - graph.coords = {i: pos for i, pos in zip(graph.nodes, coords)} - - interaction_matrix = graph.interaction_matrix() - index = {node: i for i, node in enumerate(graph.nodes)} + expected_interactions = { + (i, j): np.linalg.norm(coords_array[i] - coords_array[j]) ** (-6) + for i in range(n_nodes) + for j in range(i + 1, n_nodes) + } + expected_interaction_matrix = squareform(1 / pdist(coords_array) ** 6) - assert interaction_matrix.shape == (actual_n_nodes, actual_n_nodes) - assert np.allclose(interaction_matrix, interaction_matrix.T) - assert np.allclose(np.diag(interaction_matrix), 0.0) + interactions = graph.interactions() + assert isinstance(interactions, dict) + for (u, v), interaction in expected_interactions.items(): + np.testing.assert_allclose(interactions[(u, v)], interaction, atol=1e-8) - for (u, v), interaction in graph.interactions().items(): - assert np.isclose(interaction_matrix[index[u], index[v]], interaction) - assert np.isclose(interaction_matrix[index[v], index[u]], interaction) + np.testing.assert_allclose(graph.interaction_matrix(), expected_interaction_matrix, atol=1e-8) @pytest.mark.parametrize("n_nodes", [5, 10, 50]) From 87f88ccf762c528caefd32d72e323cd9ea29fcce Mon Sep 17 00:00:00 2001 From: Stefano Grava Date: Thu, 6 Aug 2026 11:59:49 +0200 Subject: [PATCH 3/4] bump qoolqit version to v1.3.0 --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 2 +- qoolqit/__init__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2fa5031c1..b8fb62024 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,13 +14,13 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.15.21" + rev: "v0.16.1" hooks: - id: ruff args: [--fix] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.2.0 + rev: v2.3.0 hooks: - id: mypy exclude: examples|docs diff --git a/pyproject.toml b/pyproject.toml index e11d022f9..4e46136e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "qoolqit" description = "A Python library for developing algorithms in the Rydberg Analog Model." readme = "README.md" -version = "1.2.0" +version = "1.3.0" requires-python = ">=3.10" license = { text = "MIT-derived" } keywords = ["quantum"] diff --git a/qoolqit/__init__.py b/qoolqit/__init__.py index cd11c0386..a5448f314 100644 --- a/qoolqit/__init__.py +++ b/qoolqit/__init__.py @@ -50,7 +50,7 @@ ] -__version__ = "1.2.0" +__version__ = "1.3.0" store_package_version_metadata("qoolqit", __version__) From a5322ea0295715c5ef9b51094bbb288f0306aff4 Mon Sep 17 00:00:00 2001 From: Stefano Grava Date: Thu, 6 Aug 2026 12:04:18 +0200 Subject: [PATCH 4/4] revert other branch changes --- qoolqit/graphs/base_graph.py | 20 ------------------ tests/test_graphs/test_base_graph.py | 31 ---------------------------- 2 files changed, 51 deletions(-) diff --git a/qoolqit/graphs/base_graph.py b/qoolqit/graphs/base_graph.py index e4e4ad8d7..0c360e896 100644 --- a/qoolqit/graphs/base_graph.py +++ b/qoolqit/graphs/base_graph.py @@ -5,7 +5,6 @@ import matplotlib.pyplot as plt import networkx as nx -import numpy as np from matplotlib.axes import Axes from .utils import ( @@ -247,25 +246,6 @@ 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()} - def interaction_matrix(self) -> np.ndarray: - """Rydberg model interaction 1/r^6 between pairs of nodes, as a matrix. - - Node ordering follows `self.nodes` insertion order. - The diagonal is 0, since there is no self-interaction. - - Returns: - Symmetric N x N matrix of dtype float64, where N is the number of nodes. - """ - index = {node: i for i, node in enumerate(self.nodes)} - n_nodes = len(index) - matrix = np.zeros((n_nodes, n_nodes), dtype=np.float64) - - for (u, v), interaction in self.interactions().items(): - i, j = index[u], index[v] - matrix[i, j] = matrix[j, i] = interaction - - return matrix - def min_distance(self, connected: bool | None = None) -> float: """Returns the minimum distance in the graph. diff --git a/tests/test_graphs/test_base_graph.py b/tests/test_graphs/test_base_graph.py index b4e8a0ed7..415911e96 100644 --- a/tests/test_graphs/test_base_graph.py +++ b/tests/test_graphs/test_base_graph.py @@ -5,7 +5,6 @@ import networkx as nx import numpy as np import pytest -from scipy.spatial.distance import pdist, squareform from torch_geometric.data import Data from qoolqit.graphs import BaseGraph, random_coords, random_edge_list @@ -40,14 +39,6 @@ def test_basegraph_init(n_nodes: int) -> None: with pytest.raises(AttributeError): graph.max_distance() - no_coords_match = "Trying to compute distances for a graph without coordinates." - - with pytest.raises(AttributeError, match=no_coords_match): - graph.interactions() - - with pytest.raises(AttributeError, match=no_coords_match): - graph.interaction_matrix() - with pytest.raises(AttributeError): graph.is_ud_graph() @@ -70,28 +61,6 @@ def test_basegraph_init(n_nodes: int) -> None: assert len(graph.ud_edges(radius=10.0 * scale)) == max_n_edges -@pytest.mark.parametrize("n_nodes", [3, 8, 13]) -def test_basegraph_interactions(n_nodes: int) -> None: - - rng = np.random.default_rng(0) - coords_array = rng.uniform(-1, 1, size=(n_nodes, 2)) - graph = BaseGraph.from_coordinates([c for c in coords_array]) - - expected_interactions = { - (i, j): np.linalg.norm(coords_array[i] - coords_array[j]) ** (-6) - for i in range(n_nodes) - for j in range(i + 1, n_nodes) - } - expected_interaction_matrix = squareform(1 / pdist(coords_array) ** 6) - - interactions = graph.interactions() - assert isinstance(interactions, dict) - for (u, v), interaction in expected_interactions.items(): - np.testing.assert_allclose(interactions[(u, v)], interaction, atol=1e-8) - - np.testing.assert_allclose(graph.interaction_matrix(), expected_interaction_matrix, atol=1e-8) - - @pytest.mark.parametrize("n_nodes", [5, 10, 50]) def test_basegraph_constructors(n_nodes: int) -> None: scale = ((n_nodes**0.5) ** 0.5) / 2