Skip to content

Commit d26dffc

Browse files
authored
MAINT: Hausdorff Generator handling (scipy#21897)
* MAINT: Hausdorff Generator handling * This patch is a precursor to SPEC 7 compliance of `directed_hausdorff` requested in scipygh-21833. I believe the team prefers that I first add support for `Generator` handling here, and once they are happy with that they will follow up with the usual decorator magic. * In addition to modernization of the source proper to accept a `Generator`, some test cases using a `Generator` with the "same" integer seed as the previous `RandomState` approach have been added. One such case demonstrates that defaulting to `Generator` in the long term will change some results, which SPEC 7 does acknowledge. For clarity, the returned Hausdorff distance will always remain identical, but the indices corresponding to that distance may vary according to the exact random status for this shuffling-reliant algorithm. Disruption is likely to be minimal if we follow the SPEC process/warnings timeline I'd say. Of course, if we follow the full procedure the indices returned for the same function call can change for some inputs. * Note that there are type stub/`pyi` signature(s) that might in principle be changed here but `mypy` didn't seem to care locally and IIRC our recent philosophy has been to delegate typing improvements to an external package.
1 parent 19d8fe2 commit d26dffc

File tree

3 files changed

+27
-5
lines changed

3 files changed

+27
-5
lines changed

scipy/spatial/_hausdorff.pyx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ def directed_hausdorff(const double[:,::1] ar1, const double[:,::1] ar2, seed=0)
3535
# shuffling the points in each array generally increases the likelihood of
3636
# an advantageous break in the inner search loop and never decreases the
3737
# performance of the algorithm
38-
rng = np.random.RandomState(seed)
38+
if isinstance(seed, np.random.Generator):
39+
# Generator passthrough
40+
rng = seed
41+
else:
42+
rng = np.random.RandomState(seed)
3943
resort1 = np.arange(N1, dtype=np.int64)
4044
resort2 = np.arange(N2, dtype=np.int64)
4145
rng.shuffle(resort1)

scipy/spatial/distance.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,10 @@ def directed_hausdorff(u, v, seed=0):
321321
Input array with M points in N dimensions.
322322
v : (O,N) array_like
323323
Input array with O points in N dimensions.
324-
seed : int or None, optional
324+
seed : int or `numpy.random.Generator` or None, optional
325325
Local `numpy.random.RandomState` seed. Default is 0, a random
326-
shuffling of u and v that guarantees reproducibility.
326+
shuffling of u and v that guarantees reproducibility. Also
327+
accepts a `numpy.random.Generator` object.
327328
328329
Returns
329330
-------

scipy/spatial/tests/test_hausdorff.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def test_random_state(self):
102102
new_global_state = rs2.get_state()
103103
assert_equal(new_global_state, old_global_state)
104104

105-
@pytest.mark.parametrize("seed", [None, 27870671])
105+
@pytest.mark.parametrize("seed", [None, 27870671, np.random.default_rng(177)])
106106
def test_random_state_None_int(self, seed):
107107
# check that seed values of None or int do not alter global
108108
# random state
@@ -127,12 +127,21 @@ def test_invalid_dimensions(self):
127127
# the two cases from gh-11332
128128
([(0,0)],
129129
[(0,1), (0,0)],
130-
0,
130+
np.int64(0),
131131
(0.0, 0, 1)),
132132
([(0,0)],
133133
[(0,1), (0,0)],
134134
1,
135135
(0.0, 0, 1)),
136+
# gh-11332 cases with a Generator
137+
([(0,0)],
138+
[(0,1), (0,0)],
139+
np.random.default_rng(0),
140+
(0.0, 0, 1)),
141+
([(0,0)],
142+
[(0,1), (0,0)],
143+
np.random.default_rng(1),
144+
(0.0, 0, 1)),
136145
# slightly more complex case
137146
([(-5, 3), (0,0)],
138147
[(0,1), (0,0), (-5, 3)],
@@ -141,6 +150,14 @@ def test_invalid_dimensions(self):
141150
# be the last one found, but a unique
142151
# solution is not guaranteed more broadly
143152
(0.0, 1, 1)),
153+
# repeated with Generator seeding
154+
([(-5, 3), (0,0)],
155+
[(0,1), (0,0), (-5, 3)],
156+
np.random.default_rng(77098),
157+
# NOTE: using a Generator changes the
158+
# indices but not the distance (unique solution
159+
# not guaranteed)
160+
(0.0, 0, 2)),
144161
])
145162
def test_subsets(self, A, B, seed, expected):
146163
# verify fix for gh-11332

0 commit comments

Comments
 (0)