From 74aea28fa9c2262fca2f8f18c0faa871898fb241 Mon Sep 17 00:00:00 2001 From: Liz Munch Date: Fri, 20 Mar 2026 13:32:39 -0400 Subject: [PATCH 1/2] Some additional cleanup that missed the last PR --- cereeberus/cereeberus/draw/layout.py | 30 ++++++++++++-- doc_source/images/line.png | Bin 10066 -> 16494 bytes doc_source/images/torus-extraverts.png | Bin 18223 -> 18247 bytes tests/test_draw_layout.py | 53 +++++++++++++++++++++++++ tests/test_mapper_class.py | 20 ++++++++-- tests/test_reeb_class.py | 16 +++++++- 6 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 tests/test_draw_layout.py diff --git a/cereeberus/cereeberus/draw/layout.py b/cereeberus/cereeberus/draw/layout.py index 918d9f2..18142c5 100644 --- a/cereeberus/cereeberus/draw/layout.py +++ b/cereeberus/cereeberus/draw/layout.py @@ -105,6 +105,14 @@ def reeb_x_layout(G, f, seed=None, repulsion=0.5): Returns: dict: Mapping node -> x-position. """ + + def _normalize_to_unit_interval(x): + """Normalize coordinates to [-1, 1] if there is nonzero spread.""" + x_range = x.max() - x.min() + if x_range > 1e-9: + return 2 * (x - x.min()) / x_range - 1 + return x + nodes = list(G.nodes) n = len(nodes) if n == 0: @@ -135,9 +143,23 @@ def reeb_x_layout(G, f, seed=None, repulsion=0.5): # Initialise with barycenter ordering, then add tiny jitter to break ties x0 = _barycenter_init(n, f_vals, edges, level_nodes) + + # With no edges, the spring term is absent and repulsion alone can drive + # points apart indefinitely. In this case, return the barycenter layout + # directly (normalised), which is deterministic and finite. + if len(edges) == 0: + x0 = _normalize_to_unit_interval(x0) + return {v: float(x0[idx[v]]) for v in nodes} + rng = np.random.default_rng(seed) x0 = x0 + rng.standard_normal(n) * 1e-3 + # Keep the optimizer in a compact region and add a tiny centering term so + # the objective remains well-conditioned. + bounds = [(-2.0, 2.0)] * n + center_weight = 1e-6 + x0 = np.clip(x0, -2.0, 2.0) + def energy(x): e = 0.0 for i, j in edges: @@ -146,6 +168,7 @@ def energy(x): for i, j in same_height_pairs: diff = x[i] - x[j] e += repulsion / (diff**2 + 1e-6) + e += center_weight * np.dot(x, x) return e def gradient(x): @@ -161,14 +184,13 @@ def gradient(x): grad_val = -2 * repulsion * diff / denom g[i] += grad_val g[j] -= grad_val + g += 2 * center_weight * x return g - result = minimize(energy, x0, jac=gradient, method="L-BFGS-B") + result = minimize(energy, x0, jac=gradient, method="L-BFGS-B", bounds=bounds) x_opt = result.x # Normalise to [-1, 1] - x_range = x_opt.max() - x_opt.min() - if x_range > 1e-9: - x_opt = 2 * (x_opt - x_opt.min()) / x_range - 1 + x_opt = _normalize_to_unit_interval(x_opt) return {v: float(x_opt[idx[v]]) for v in nodes} diff --git a/doc_source/images/line.png b/doc_source/images/line.png index 24939ba33b6ff9e8a62cdd1327e168df1b655f37..62f6490f42fac77dd81e12bcebb461314c11d16d 100644 GIT binary patch literal 16494 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 literal 10066 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 F{{iclIhOzc diff --git a/doc_source/images/torus-extraverts.png b/doc_source/images/torus-extraverts.png index 9dc36efa3a339270868aabd4326e88aff6b43583..c3e71da4c8877d6456ee91be8ce032c05d2c44ee 100644 GIT binary patch literal 18247 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 Iol`;+0HEn=Hvj+t literal 18223 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 diff --git a/tests/test_draw_layout.py b/tests/test_draw_layout.py new file mode 100644 index 0000000..f9da6b1 --- /dev/null +++ b/tests/test_draw_layout.py @@ -0,0 +1,53 @@ +import unittest + +import networkx as nx +import numpy as np +from cereeberus.data import ex_reebgraphs as ex_rg +from cereeberus.draw.layout import reeb_x_layout + + +class TestDrawLayout(unittest.TestCase): + def test_reeb_x_layout_returns_finite_x_for_all_nodes(self): + R = ex_rg.torus(multigraph=False, seed=0) + + x_positions = reeb_x_layout(R, R.f, seed=17, repulsion=0.8) + + self.assertEqual(set(x_positions.keys()), set(R.nodes)) + for v in R.nodes: + self.assertTrue(np.isfinite(x_positions[v])) + self.assertLessEqual(x_positions[v], 1.0 + 1e-9) + self.assertGreaterEqual(x_positions[v], -1.0 - 1e-9) + + def test_reeb_x_layout_is_reproducible_with_seed(self): + R = ex_rg.torus(multigraph=False, seed=0) + + x_a = reeb_x_layout(R, R.f, seed=123, repulsion=0.8) + x_b = reeb_x_layout(R, R.f, seed=123, repulsion=0.8) + + for v in R.nodes: + self.assertAlmostEqual(x_a[v], x_b[v]) + + def test_reeb_x_layout_empty_graph(self): + G = nx.Graph() + x_positions = reeb_x_layout(G, {}, seed=1, repulsion=0.5) + self.assertEqual(x_positions, {}) + + def test_reeb_x_layout_same_height_isolated_nodes(self): + # Regression test: no edges + same-height nodes should not trigger + # unbounded optimisation drift. + G = nx.Graph() + nodes = ["u", "v", "w", "z"] + G.add_nodes_from(nodes) + f = {node: 0.0 for node in nodes} + + x_positions = reeb_x_layout(G, f, seed=9, repulsion=1.0) + + self.assertEqual(set(x_positions.keys()), set(nodes)) + for node in nodes: + self.assertTrue(np.isfinite(x_positions[node])) + self.assertLessEqual(x_positions[node], 1.0 + 1e-9) + self.assertGreaterEqual(x_positions[node], -1.0 - 1e-9) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_mapper_class.py b/tests/test_mapper_class.py index 7f20c5e..d9ab4f6 100644 --- a/tests/test_mapper_class.py +++ b/tests/test_mapper_class.py @@ -1,9 +1,12 @@ import unittest -from cereeberus import ReebGraph, MapperGraph + +import numpy as np from cereeberus.data import ex_graphs as ex_g -from cereeberus.data import ex_reebgraphs as ex_rg from cereeberus.data import ex_mappergraphs as ex_mg -import numpy as np +from cereeberus.data import ex_reebgraphs as ex_rg + +from cereeberus import MapperGraph, ReebGraph + class TestMapperClass(unittest.TestCase): @@ -129,8 +132,19 @@ def test_dist_matrix(self): # Check the whole put together matrix M = R.thickening_distance_matrix() self.assertEqual(M[5][3,10], np.inf) + + def test_set_pos_from_f_preserves_delta_scaled_y_values(self): + # Mapper layout should keep y = delta * f(v), with repulsion only affecting x. + MG = ex_mg.torus(delta=0.2, seed=11) + MG.set_pos_from_f(seed=5, repulsion=0.9) + + self.assertEqual(set(MG.nodes), set(MG.pos_f.keys())) + for v in MG.nodes: + self.assertEqual(MG.pos_f[v][1], MG.delta * MG.f[v]) +if __name__ == '__main__': + unittest.main() if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/test_reeb_class.py b/tests/test_reeb_class.py index 0f5cb9c..24c47eb 100644 --- a/tests/test_reeb_class.py +++ b/tests/test_reeb_class.py @@ -1,8 +1,11 @@ import unittest -from cereeberus import ReebGraph + from cereeberus.data import ex_graphs as ex_g from cereeberus.data import ex_reebgraphs as ex_rg +from cereeberus import ReebGraph + + class TestReebClass(unittest.TestCase): def check_reeb(self, R): @@ -227,11 +230,22 @@ def test_matrices(self): B = R.boundary_matrix() self.assertEqual(B.shape, (len(R.nodes), len(R.edges))) + def test_set_pos_from_f_preserves_y_function_values(self): + # The constrained layout updates x-coordinates only; y should remain f(v). + R = ex_rg.torus(multigraph=False) + R.set_pos_from_f(seed=3, repulsion=1.2) + + self.assertEqual(set(R.nodes), set(R.pos_f.keys())) + for v in R.nodes: + self.assertEqual(R.pos_f[v][1], R.f[v]) + +if __name__ == '__main__': + unittest.main() if __name__ == '__main__': unittest.main() \ No newline at end of file From e1b895cfd60027ce4444b679598f176fb701a839 Mon Sep 17 00:00:00 2001 From: Liz Munch Date: Fri, 20 Mar 2026 13:33:46 -0400 Subject: [PATCH 2/2] Increment number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 064dc66..ea0c6e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cereeberus" -version = "0.1.13" +version = "0.1.14" authors = [ { name="Liz Munch", email="muncheli@msu.edu" }, ]