def test_scale(self): # 1d scalar space = [[0], [1], [0.5]] out = [[-2], [6], [2]] scaled_space = qmc.scale(space, l_bounds=-2, u_bounds=6) assert_allclose(scaled_space, out) # 2d space space = [[0, 0], [1, 1], [0.5, 0.5]] bounds = np.array([[-2, 0], [6, 5]]) out = [[-2, 0], [6, 5], [2, 2.5]] scaled_space = qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) assert_allclose(scaled_space, out) scaled_back_space = qmc.scale(scaled_space, l_bounds=bounds[0], u_bounds=bounds[1], reverse=True) assert_allclose(scaled_back_space, space) # broadcast space = [[0, 0, 0], [1, 1, 1], [0.5, 0.5, 0.5]] l_bounds, u_bounds = 0, [6, 5, 3] out = [[0, 0, 0], [6, 5, 3], [3, 2.5, 1.5]] scaled_space = qmc.scale(space, l_bounds=l_bounds, u_bounds=u_bounds) assert_allclose(scaled_space, out)
def test_scale_random(self): rng = np.random.default_rng(317589836511269190194010915937762468165) sample = rng.random((30, 10)) a = -rng.random(10) * 10 b = rng.random(10) * 10 scaled = qmc.scale(sample, a, b, reverse=False) unscaled = qmc.scale(scaled, a, b, reverse=True) assert_allclose(unscaled, sample)
def test_scale_random(self): np.random.seed(0) sample = np.random.rand(30, 10) a = -np.random.rand(10) * 10 b = np.random.rand(10) * 10 scaled = qmc.scale(sample, a, b, reverse=False) unscaled = qmc.scale(scaled, a, b, reverse=True) assert_allclose(unscaled, sample)
def test_scale_errors(self): with pytest.raises(ValueError, match=r"Sample is not a 2D array"): space = [0, 1, 0.5] qmc.scale(space, l_bounds=-2, u_bounds=6) with pytest.raises(ValueError, match=r"Bounds are not consistent" r" a < b"): space = [[0, 0], [1, 1], [0.5, 0.5]] bounds = np.array([[-2, 6], [6, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"shape mismatch: objects cannot " r"be broadcast to a " r"single shape"): space = [[0, 0], [1, 1], [0.5, 0.5]] l_bounds, u_bounds = [-2, 0, 2], [6, 5] qmc.scale(space, l_bounds=l_bounds, u_bounds=u_bounds) with pytest.raises(ValueError, match=r"Sample dimension is different " r"than bounds dimension"): space = [[0, 0], [1, 1], [0.5, 0.5]] bounds = np.array([[-2, 0, 2], [6, 5, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"Sample is not in unit " r"hypercube"): space = [[0, 0], [1, 1.5], [0.5, 0.5]] bounds = np.array([[-2, 0], [6, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"Sample is out of bounds"): out = [[-2, 0], [6, 5], [8, 2.5]] bounds = np.array([[-2, 0], [6, 5]]) qmc.scale(out, l_bounds=bounds[0], u_bounds=bounds[1], reverse=True)
"""Calculate the discrepancy of 2 designs and compare them.""" import numpy as np from scipy.stats import qmc import matplotlib.pyplot as plt space_1 = np.array([[1, 3], [2, 6], [3, 2], [4, 5], [5, 1], [6, 4]]) space_2 = np.array([[1, 5], [2, 4], [3, 3], [4, 2], [5, 1], [6, 6]]) l_bounds = [0.5, 0.5] u_bounds = [6.5, 6.5] space_1 = qmc.scale(space_1, l_bounds, u_bounds, reverse=True) space_2 = qmc.scale(space_2, l_bounds, u_bounds, reverse=True) sample = {'space_1': space_1, 'space_2': space_2} fig, axs = plt.subplots(1, 2, figsize=(8, 4)) for i, kind in enumerate(sample): axs[i].scatter(sample[kind][:, 0], sample[kind][:, 1]) axs[i].set_aspect('equal') axs[i].set_xlabel(r'$x_1$') axs[i].set_ylabel(r'$x_2$') axs[i].set_title(f'{kind}—$C^2 = ${qmc.discrepancy(sample[kind]):.5}') plt.tight_layout() plt.show()
def draw_exploration_sample( x, lower, upper, n_samples, sampling_distribution, sampling_method, seed, ): """Get a sample of parameter values for the first stage of the tiktak algorithm. The sample is created randomly or using a low discrepancy sequence. Different distributions are available. Args: x (np.ndarray): Internal parameter vector of shape (n_params,). lower (np.ndarray): Vector of internal lower bounds of shape (n_params,). upper (np.ndarray): Vector of internal upper bounds of shape (n_params,). n_samples (int): Number of sample points on which one function evaluation shall be performed. Default is 10 * n_params. sampling_distribution (str): One of "uniform", "triangular". Default is "uniform", as in the original tiktak algorithm. sampling_method (str): One of "sobol", "halton", "latin_hypercube" or "random". Default is sobol for problems with up to 200 parameters and random for problems with more than 200 parameters. seed (int): Random seed. Returns: np.ndarray: Numpy array of shape (n_samples, n_params). Each row represents a vector of parameter values. """ valid_rules = ["sobol", "halton", "latin_hypercube", "random"] valid_distributions = ["uniform", "triangular"] if sampling_method not in valid_rules: raise ValueError( f"Invalid rule: {sampling_method}. Must be one of\n\n{valid_rules}\n\n" ) if sampling_distribution not in valid_distributions: raise ValueError(f"Unsupported distribution: {sampling_distribution}") for name, bound in zip(["lower", "upper"], [lower, upper]): if not np.isfinite(bound).all(): raise ValueError( f"multistart optimization requires finite {name}_bounds or " f"soft_{name}_bounds for all parameters.") if sampling_method == "sobol": # Draw `n` points from the open interval (lower, upper)^d. # Note that scipy uses the half-open interval [lower, upper)^d internally. # We apply a burn-in phase of 1, i.e. we skip the first point in the sequence # and thus exclude the lower bound. sampler = qmc.Sobol(d=len(lower), scramble=False, seed=seed) _ = sampler.fast_forward(1) sample_unscaled = sampler.random(n=n_samples) elif sampling_method == "halton": sampler = qmc.Halton(d=len(lower), scramble=False, seed=seed) sample_unscaled = sampler.random(n=n_samples) elif sampling_method == "latin_hypercube": sampler = qmc.LatinHypercube(d=len(lower), strength=1, seed=seed) sample_unscaled = sampler.random(n=n_samples) elif sampling_method == "random": rng = get_rng(seed) sample_unscaled = rng.uniform(size=(n_samples, len(lower))) if sampling_distribution == "uniform": sample_scaled = qmc.scale(sample_unscaled, lower, upper) elif sampling_distribution == "triangular": sample_scaled = triang.ppf( sample_unscaled, c=(x - lower) / (upper - lower), loc=lower, scale=upper - lower, ) return sample_scaled
def test_scale_errors(self): with pytest.raises(ValueError, match=r"Sample is not a 2D array"): space = [0, 1, 0.5] qmc.scale(space, l_bounds=-2, u_bounds=6) with pytest.raises(ValueError, match=r"Bounds are not consistent"): space = [[0, 0], [1, 1], [0.5, 0.5]] bounds = np.array([[-2, 6], [6, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"'l_bounds' and 'u_bounds'" r" must be broadcastable"): space = [[0, 0], [1, 1], [0.5, 0.5]] l_bounds, u_bounds = [-2, 0, 2], [6, 5] qmc.scale(space, l_bounds=l_bounds, u_bounds=u_bounds) with pytest.raises(ValueError, match=r"'l_bounds' and 'u_bounds'" r" must be broadcastable"): space = [[0, 0], [1, 1], [0.5, 0.5]] bounds = np.array([[-2, 0, 2], [6, 5, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"Sample is not in unit " r"hypercube"): space = [[0, 0], [1, 1.5], [0.5, 0.5]] bounds = np.array([[-2, 0], [6, 5]]) qmc.scale(space, l_bounds=bounds[0], u_bounds=bounds[1]) with pytest.raises(ValueError, match=r"Sample is out of bounds"): out = [[-2, 0], [6, 5], [8, 2.5]] bounds = np.array([[-2, 0], [6, 5]]) qmc.scale(out, l_bounds=bounds[0], u_bounds=bounds[1], reverse=True)