diff --git a/README.md b/README.md index 61913c41..7b7f0dd0 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Graphs belong to the class `UGraph` or `DGraph` for undirected or directed graph *** - `g.adjacency()` adjacency matrix -- `g.Laplacian()` Laplacian matrix +- `g.laplacian()` Laplacian matrix - `g.incidence()` incidence matrix ### Properties and methods of a vertex diff --git a/docs/source/matrices.rst b/docs/source/matrices.rst index dcf9d3d6..d323b756 100644 --- a/docs/source/matrices.rst +++ b/docs/source/matrices.rst @@ -4,4 +4,4 @@ Convert graph to matrix form These methods convert a graph to a matrix representation . .. autoclass:: pgraph.PGraph._BaseGraph - :members: distance, adjacency, degree, incidence, Laplacian + :members: distance, adjacency, degree, incidence, laplacian diff --git a/src/pgraph/PGraph.py b/src/pgraph/PGraph.py index d44b815a..61efcdf3 100644 --- a/src/pgraph/PGraph.py +++ b/src/pgraph/PGraph.py @@ -1966,14 +1966,14 @@ def average_degree(self) -> float: # MATRIX REPRESENTATIONS - def Laplacian(self) -> NDArray: + def laplacian(self) -> NDArray: """ Laplacian matrix for the graph :return: Laplacian matrix :rtype: NumPy ndarray - ``g.Laplacian()`` is the Laplacian matrix (NxN) of the graph where N + ``g.laplacian()`` is the Laplacian matrix (NxN) of the graph where N is the number of vertices. .. runblock:: pycon @@ -1987,7 +1987,7 @@ def Laplacian(self) -> NDArray: >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: ... g.add_edge(g[i], g[j]) ... - >>> L = g.Laplacian() + >>> L = g.laplacian() >>> print(L) .. note:: @@ -2023,6 +2023,24 @@ def Laplacian(self) -> NDArray: """ return np.diag([v.out_degree for v in self]) - self.adjacency() + def Laplacian(self) -> NDArray: + """ + Laplacian matrix for the graph (deprecated) + + .. deprecated:: 1.0 + Use :meth:`laplacian` instead. A capitalized name denotes a class + method that constructs a graph, like :meth:`Adjacency` and + :meth:`Dict`; this is an instance method that returns a matrix. + + :seealso: :meth:`laplacian` + """ + warnings.warn( + "Laplacian() is deprecated, use laplacian() instead", + DeprecationWarning, + stacklevel=2, + ) + return self.laplacian() + def connectivity(self, vertices: Iterable[BaseVertex] | None = None) -> list[int]: """ Graph connectivity @@ -2094,7 +2112,7 @@ def degree(self) -> NDArray: For a ``DGraph`` this was the out-degree matrix, counting only the edges leaving each vertex. It is now the total, in plus out. The old matrix is ``np.diag([v.out_degree for v in g])``, which is what - :meth:`Laplacian` uses. + :meth:`laplacian` uses. .. runblock:: pycon @@ -2116,7 +2134,7 @@ def degree(self) -> NDArray: add ``k`` to the degree of each end. A self-loop adds 2, for both kinds of graph, since it has two ends. - :seealso: :meth:`adjacency` :meth:`incidence` :meth:`Laplacian` + :seealso: :meth:`adjacency` :meth:`incidence` :meth:`laplacian` :meth:`connectivity` :meth:`average_degree` :attr:`BaseVertex.degree` """ @@ -2166,7 +2184,7 @@ def adjacency(self) -> NDArray: (consistent with :meth:`degree`) and 1 for a ``DGraph``. In every case each row of ``A`` sums to the degree of that vertex. - :seealso: :meth:`Laplacian` :meth:`incidence` :meth:`degree` + :seealso: :meth:`laplacian` :meth:`incidence` :meth:`degree` """ # create a dict mapping vertex to an id vdict = {} @@ -2222,7 +2240,7 @@ def incidence(self) -> NDArray: so row sums still equal :meth:`degree`); for a ``DGraph`` it holds a single 1. - :seealso: :meth:`Laplacian` :meth:`adjacency` :meth:`degree` + :seealso: :meth:`laplacian` :meth:`adjacency` :meth:`degree` """ edges = self.edges() I = np.zeros((self.n, len(edges))) diff --git a/tests/test_graph.py b/tests/test_graph.py index 94fc4f34..8003a0b5 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -588,7 +588,7 @@ def test_matrices(self): D = g.degree() nt.assert_array_equal(D, np.diag([2, 1, 1, 0])) - L = g.Laplacian() + L = g.laplacian() expected_L = np.diag([2, 1, 1, 0]) - expected_A nt.assert_array_equal(L, expected_L) @@ -902,7 +902,7 @@ def test_matrices(self): # the directed Laplacian uses the OUT-degree, D_out - A, whose rows # sum to zero; it is not degree() - A for a directed graph - L = g.Laplacian() + L = g.laplacian() expected_L = np.diag([2, 1, 0]) - expected_A nt.assert_array_equal(L, expected_L) nt.assert_array_equal(L.sum(axis=1), np.zeros(3)) diff --git a/tests/test_in_edges.py b/tests/test_in_edges.py index 3ae807ff..7f9be411 100644 --- a/tests/test_in_edges.py +++ b/tests/test_in_edges.py @@ -65,7 +65,7 @@ def test_average_degree(self): def test_laplacian_still_has_zero_row_sums(self): # a directed Laplacian uses the OUT-degree: D_out - A - L = self.g.Laplacian() + L = self.g.laplacian() np.testing.assert_allclose(L.sum(axis=1), 0) np.testing.assert_array_equal( L, np.diag([v.out_degree for v in self.g]) - self.g.adjacency() diff --git a/tests/test_laplacian_name.py b/tests/test_laplacian_name.py new file mode 100644 index 00000000..f605f243 --- /dev/null +++ b/tests/test_laplacian_name.py @@ -0,0 +1,43 @@ +"""``laplacian()`` is canonical; ``Laplacian()`` is a deprecated alias.""" + +import unittest +import warnings + +import numpy as np + +from pgraph import DGraph, UGraph + + +class _Name: + cls: type + + def setUp(self): + self.g = self.cls() + a, b, c = (self.g.add_vertex() for _ in range(3)) + self.g.add_edge(a, b) + self.g.add_edge(b, c) + + def test_lowercase_does_not_warn(self): + with warnings.catch_warnings(): + warnings.simplefilter("error") + L = self.g.laplacian() + self.assertEqual(L.shape, (3, 3)) + + def test_capitalized_warns_and_agrees(self): + with self.assertWarns(DeprecationWarning) as cm: + L = self.g.Laplacian() + self.assertIn("laplacian()", str(cm.warning)) + self.assertEqual(cm.filename, __file__) # stacklevel points at caller + np.testing.assert_array_equal(L, self.g.laplacian()) + + +class TestUGraphName(_Name, unittest.TestCase): + cls = UGraph + + +class TestDGraphName(_Name, unittest.TestCase): + cls = DGraph + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_multigraph.py b/tests/test_multigraph.py index c1883d2a..9199dec7 100644 --- a/tests/test_multigraph.py +++ b/tests/test_multigraph.py @@ -19,7 +19,7 @@ towards the (out-)degree of a ``DGraph`` vertex - ``adjacency()`` counts edges, so ``A[i, j]`` is the number of edges from ``i`` to ``j`` and each row of ``A`` sums to the degree of the vertex -- ``Laplacian()`` is therefore ``degree() - adjacency()``, rows sum to zero +- ``laplacian()`` is therefore ``degree() - adjacency()``, rows sum to zero - ``distance()`` and path lengths use the cheapest of any parallel edges """ @@ -118,7 +118,7 @@ def check_matrices_consistent(self): np.testing.assert_array_equal(out, deg) # the Laplacian is D_out - A, so each row sums to zero for any graph - L = g.Laplacian() + L = g.laplacian() np.testing.assert_array_equal(L, np.diag(out) - A) np.testing.assert_allclose(L.sum(axis=1), 0) # and degrees sum to twice the edges (every edge has two ends)