diff --git a/CHANGELOG.md b/CHANGELOG.md index c901c472..5f8e66ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +- Fix `np.shuffle` crash and wrong return type in `Searchspace` distributed random and LHS samplers ## [1.5.0] - 2026-08-20 - Kernel Tuner can now be used from Julia, [see KernelTuner.jl](https://github.com/KernelTuner/KernelTuner.jl) diff --git a/kernel_tuner/searchspace.py b/kernel_tuner/searchspace.py index f38a32cc..9b91302d 100644 --- a/kernel_tuner/searchspace.py +++ b/kernel_tuner/searchspace.py @@ -1267,13 +1267,13 @@ def get_distributed_random_sample_indices(self, num_samples: int, sampling_facto ) num_samples = round(self.size / 2) if num_samples == self.size: - return np.shuffle([range(self.size)]) + return np.random.permutation(self.size).tolist() # adjust the number of random samples if necessary sampling_factor = max(1, sampling_factor) num_random_samples = min(sampling_factor * num_samples, self.size) if num_random_samples == self.size or num_random_samples <= 1: - return self.get_random_sample(num_random_samples) + return self.get_random_sample_indices(num_samples).tolist() random_samples_indices = self.get_random_sample_indices(num_random_samples) # calculate the desired parameter configuration indices, starting at the edges of the parameter indices and halving each time @@ -1340,7 +1340,7 @@ def get_LHS_sample_indices(self, num_samples: int) -> List[int]: ) num_samples = round(self.size / 2) if num_samples == self.size: - return np.shuffle([range(self.size)]) + return np.random.permutation(self.size).tolist() if self.params_values_indices is None: self.__prepare_neighbors_index() diff --git a/test/test_searchspace.py b/test/test_searchspace.py index b1102bd3..ded4d82b 100644 --- a/test/test_searchspace.py +++ b/test/test_searchspace.py @@ -9,6 +9,7 @@ from unittest.mock import patch import numpy as np +import pytest from constraint import ExactSumConstraint from kernel_tuner.interface import Options @@ -562,6 +563,19 @@ def test_get_distributed_random_sample(): for index in distributed_random_sample_indices: assert 0 <= index < searchspace.size + # check that requesting exactly the searchspace size returns every index exactly once + full_sample_indices = searchspace.get_distributed_random_sample_indices(num_samples=searchspace.size) + assert len(full_sample_indices) == searchspace.size + assert sorted(full_sample_indices) == list(range(searchspace.size)) + + # check that requesting more than the searchspace size warns and reduces the sample size + with pytest.warns(UserWarning): + oversized_sample_indices = searchspace.get_distributed_random_sample_indices(num_samples=searchspace.size + 6) + assert len(oversized_sample_indices) == round(searchspace.size / 2) + assert len(set(oversized_sample_indices)) == len(oversized_sample_indices) + for index in oversized_sample_indices: + assert 0 <= index < searchspace.size + def test_get_LHS_sample_indices(): """Test whether the distributed random sample indices are as expected.""" # create a searchspace with mixed parameter types @@ -588,6 +602,19 @@ def test_get_LHS_sample_indices(): for index in distributed_random_sample_indices: assert 0 <= index < searchspace.size + # check that requesting exactly the searchspace size returns every index exactly once + full_sample_indices = searchspace.get_LHS_sample_indices(num_samples=searchspace.size) + assert len(full_sample_indices) == searchspace.size + assert sorted(full_sample_indices) == list(range(searchspace.size)) + + # check that requesting more than the searchspace size warns and reduces the sample size + with pytest.warns(UserWarning): + oversized_sample_indices = searchspace.get_LHS_sample_indices(num_samples=searchspace.size + 6) + assert len(oversized_sample_indices) == round(searchspace.size / 2) + assert len(set(oversized_sample_indices)) == len(oversized_sample_indices) + for index in oversized_sample_indices: + assert 0 <= index < searchspace.size + def test_small_searchspace(): """Test a small real-world searchspace and the usage of the `max_threads` parameter.""" max_threads = 1024