@@ -253,3 +253,58 @@ def test_edge_positions_from_barycenters(edgelist1):
253253 for idx , e in H .edges .members (dtype = dict ).items ():
254254 mean_pos = np .mean ([node_pos [n ] for n in e ], axis = 0 )
255255 assert np .allclose (edge_pos [idx ], mean_pos )
256+
257+
258+ def test_spring_layouts_accept_rng (edgelist1 ):
259+ """Spring layouts should accept np.random.Generator as seed (issue #712)."""
260+ H = xgi .Hypergraph (edgelist1 )
261+ rng = np .random .default_rng (42 )
262+
263+ # All four spring layouts should accept a Generator without erroring
264+ xgi .pairwise_spring_layout (H , seed = rng )
265+ xgi .barycenter_spring_layout (H , seed = rng )
266+ xgi .weighted_barycenter_spring_layout (H , seed = rng )
267+ xgi .bipartite_spring_layout (H , seed = rng )
268+
269+ # int seed still works
270+ xgi .barycenter_spring_layout (H , seed = 42 )
271+ # None still works
272+ xgi .barycenter_spring_layout (H , seed = None )
273+
274+
275+ def test_spring_layouts_rng_semantics (edgelist1 ):
276+ """Document expected reproducibility semantics for the seed argument.
277+
278+ The conventions mirror sklearn / scipy:
279+
280+ * Reusing the same `Generator` instance advances its state, so two
281+ consecutive calls produce different layouts.
282+ * Two fresh `Generator`s constructed from the same seed produce identical
283+ layouts.
284+ * Passing an int seed reproduces itself, but is not equivalent to passing a
285+ `Generator` constructed from that same int (the conversion path differs).
286+ """
287+ H = xgi .Hypergraph (edgelist1 )
288+
289+ # Reusing one rng across calls: state advances → different outputs
290+ rng = np .random .default_rng (42 )
291+ pos_a = xgi .barycenter_spring_layout (H , seed = rng )
292+ pos_b = xgi .barycenter_spring_layout (H , seed = rng )
293+ assert any (not np .allclose (pos_a [n ], pos_b [n ]) for n in pos_a )
294+
295+ # Two fresh rngs from the same seed → identical outputs
296+ pos_c = xgi .barycenter_spring_layout (H , seed = np .random .default_rng (42 ))
297+ pos_d = xgi .barycenter_spring_layout (H , seed = np .random .default_rng (42 ))
298+ for n in pos_c :
299+ assert np .allclose (pos_c [n ], pos_d [n ])
300+
301+ # Same int seed reused → identical outputs
302+ pos_e = xgi .barycenter_spring_layout (H , seed = 42 )
303+ pos_f = xgi .barycenter_spring_layout (H , seed = 42 )
304+ for n in pos_e :
305+ assert np .allclose (pos_e [n ], pos_f [n ])
306+
307+ # int seed vs rng built from same seed → NOT equivalent (different paths)
308+ pos_g = xgi .barycenter_spring_layout (H , seed = 42 )
309+ pos_h = xgi .barycenter_spring_layout (H , seed = np .random .default_rng (42 ))
310+ assert any (not np .allclose (pos_g [n ], pos_h [n ]) for n in pos_g )
0 commit comments