From 0a9b654cc41e18938d844703b8bcefe2c0f07ee9 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Tue, 13 Oct 2026 10:52:38 +1000 Subject: [PATCH] perf: a vertex makes its coordinate with np.array, not np.r_ np.r_[coord] is a class that parses its index and cost 1.4 us, 88% of creating a vertex. np.array(coord, ndmin=1) costs 0.16 us and gives the same array for every coordinate checked (a list, a tuple, a scalar, a numpy array of either type, a range, a 2-D list): same shape, type and values, and a copy. Vertex(coord) 1.59 -> 0.35 us; add_vertex(coord) 3.12 -> 1.72 us, where 0.6.5 took 2.18 us. Co-Authored-By: Claude Sonnet 5.5 --- src/pgraph/PGraph.py | 3 ++- tests/test_vertex_init.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/pgraph/PGraph.py b/src/pgraph/PGraph.py index 956fd0e8..21266597 100644 --- a/src/pgraph/PGraph.py +++ b/src/pgraph/PGraph.py @@ -4518,7 +4518,8 @@ def __init__(self, coord: ArrayLike | None = None, name: str | None = None): # invalidate; writing the attributes directly avoids the cost of # ``__setattr__`` on each of them. self.__dict__.update( - coord=None if coord is None else np.r_[coord], + # np.array is a tenth of the cost of np.r_ for the same result here + coord=None if coord is None else np.array(coord, ndmin=1), _name=name, _label=None, _connectivitychange=True, diff --git a/tests/test_vertex_init.py b/tests/test_vertex_init.py index a86452bb..9a4a3cf7 100644 --- a/tests/test_vertex_init.py +++ b/tests/test_vertex_init.py @@ -39,6 +39,37 @@ def test_the_coordinate_is_a_float_array_copy(self): self.assertEqual(v.coord[0], 1.0) # not the caller's array self.assertIsInstance(v.coord, np.ndarray) + def test_the_coordinate_is_what_np_r_made(self): + # the coordinate used to be made with np.r_[coord]; for everything a + # coordinate can be it is the same array, in shape, type and values + for coord in ( + [1, 2], + [1.5, 2], + (1, 2, 3), + 5, + 2.5, + np.array([1.0, 2.0]), + np.array([1, 2], dtype=np.int32), + range(3), + [[1, 2], [3, 4]], + ): + with self.subTest(coord=repr(coord)): + v = Vertex(coord=coord) + expected = np.r_[coord] + self.assertEqual(v.coord.shape, expected.shape) + self.assertEqual(v.coord.dtype, expected.dtype) + np.testing.assert_array_equal(v.coord, expected) + + def test_a_scalar_is_a_vector_of_one(self): + self.assertEqual(Vertex(coord=7).coord.shape, (1,)) + + def test_an_array_given_is_copied(self): + c = np.array([1, 2]) + v = Vertex(coord=c) + c[0] = 99 + self.assertEqual(v.coord[0], 1) + self.assertFalse(np.shares_memory(v.coord, c)) + def test_the_lists_are_not_shared_between_vertices(self): a, b = Vertex(), Vertex() self.assertIsNot(a._edgelist, b._edgelist)