diff --git a/src/probnum/quad/_utils.py b/src/probnum/quad/_utils.py index 5e682aec34..4e7c5a1a5c 100644 --- a/src/probnum/quad/_utils.py +++ b/src/probnum/quad/_utils.py @@ -53,11 +53,12 @@ def as_domain( """ if input_dim is not None and input_dim < 1: raise ValueError( - f"Input dimension must be positive. Current value is ({input_dim})." + f"If given, input dimension must be positive. Current value " + f"is ({input_dim})." ) if len(domain) != 2: - raise ValueError(f"'domain' must be of length 2 ({len(domain)}).") + raise ValueError(f"domain must be of length 2 ({len(domain)}).") # Domain limits must have equal dimensions if np.size(domain[0]) != np.size(domain[1]): @@ -84,8 +85,8 @@ def as_domain( # Size of domain and input dimension do not match if input_dim != domain_dim: raise ValueError( - f"If domain limits are not scalars, their lengths " - f"must match the input dimension ({input_dim})." + "If domain limits are not scalars, their lengths " + "must match the input dimension." ) domain_a = domain[0] domain_b = domain[1] diff --git a/src/probnum/quad/integration_measures/_integration_measure.py b/src/probnum/quad/integration_measures/_integration_measure.py index e8e7f9da44..2bf8c0410e 100644 --- a/src/probnum/quad/integration_measures/_integration_measure.py +++ b/src/probnum/quad/integration_measures/_integration_measure.py @@ -54,7 +54,7 @@ def __call__(self, points: Union[FloatLike, np.ndarray]) -> np.ndarray: def sample( self, n_sample: IntLike, - rng: np.random.Generator, + rng: Optional[np.random.Generator] = np.random.default_rng(), ) -> np.ndarray: """Sample ``n_sample`` points from the integration measure. @@ -63,7 +63,7 @@ def sample( n_sample Number of points to be sampled rng - A Random number generator. + Random number generator. Optional. Default is `np.random.default_rng()`. Returns ------- diff --git a/src/probnum/quad/integration_measures/_lebesgue_measure.py b/src/probnum/quad/integration_measures/_lebesgue_measure.py index 27a5bed701..0d96da8461 100644 --- a/src/probnum/quad/integration_measures/_lebesgue_measure.py +++ b/src/probnum/quad/integration_measures/_lebesgue_measure.py @@ -65,7 +65,7 @@ def __call__(self, points: np.ndarray) -> np.ndarray: def sample( self, n_sample: IntLike, - rng: np.random.Generator, + rng: Optional[np.random.Generator] = np.random.default_rng(), ) -> np.ndarray: return self.random_variable.rvs( size=(n_sample, self.input_dim), random_state=rng diff --git a/tests/test_quad/test_bq_utils.py b/tests/test_quad/test_bq_utils.py index 60728b06e7..9fd145bb9a 100644 --- a/tests/test_quad/test_bq_utils.py +++ b/tests/test_quad/test_bq_utils.py @@ -10,15 +10,12 @@ @pytest.mark.parametrize( "dom, in_dim", [ - ((0, 1), 0), # zero dimension ((0, 1), -2), # negative dimension ((np.zeros(2), np.ones(2)), 3), # length of bounds does not match dimension ((np.zeros(2), np.ones(3)), None), # lower and upper bounds not equal lengths ((np.array([0, 0]), np.array([1, 0])), None), # integration domain is empty ((np.zeros([2, 1]), np.ones([2, 1])), None), # bounds have too many dimensions ((np.zeros([2, 1]), np.ones([2, 1])), 2), # bounds have too many dimensions - ((0, 1, 2), 2), # domain has too many elements - ((-np.ones(2), np.zeros(2), np.ones(2)), 2), # domain has too many elements ] ) def test_as_domain_wrong_input(dom, in_dim): diff --git a/tests/test_quad/test_integration_measure.py b/tests/test_quad/test_integration_measure.py index 6b1e8351e2..7596a2167b 100644 --- a/tests/test_quad/test_integration_measure.py +++ b/tests/test_quad/test_integration_measure.py @@ -5,25 +5,9 @@ from probnum.quad.integration_measures import GaussianMeasure, LebesgueMeasure -# Tests shared by all measures start here - -def test_density_call_shape(x, measure): - expected_shape = (x.shape[0],) - assert measure(x).shape == expected_shape - - -@pytest.mark.parametrize("n_sample", [1, 2, 5]) -def test_sample_shape(measure, n_sample, rng): - input_dim = measure.input_dim - res = measure.sample(n_sample=n_sample, rng=rng) - assert res.shape == (n_sample, input_dim) - - -# Tests for Gaussian measure start here - - -def test_gaussian_diagonal_covariance(input_dim): +# Tests for Gaussian measure +def test_gaussian_diagonal_covariance(input_dim: int): """Check that diagonal covariance matrices are recognised as diagonal.""" mean = np.full((input_dim,), 0.0) cov = np.eye(input_dim) @@ -52,6 +36,13 @@ def test_gaussian_mean_shape_1d(mean, cov): assert measure.cov.size == 1 +@pytest.mark.parametrize("neg_dim", [0, -1, -10, -100]) +def test_gaussian_negative_dimension(neg_dim): + """Make sure that a negative dimension raises ValueError.""" + with pytest.raises(ValueError): + GaussianMeasure(0, 1, neg_dim) + + def test_gaussian_param_assignment(input_dim: int): """Check that diagonal mean and covariance for higher dimensions are extended correctly.""" @@ -66,24 +57,15 @@ def test_gaussian_param_assignment(input_dim: int): assert np.array_equal(measure.cov, np.eye(input_dim)) -def test_gaussian_param_assignment_scalar(): +def test_gaussian_scalar(): """Check that the 1d Gaussian case works.""" measure = GaussianMeasure(0.5, 1.5) assert measure.mean == 0.5 assert measure.cov == 1.5 -@pytest.mark.parametrize("wrong_dim", [0, -1, -10, -100]) -def test_gaussian_wrong_dimension_raises(wrong_dim): - """Make sure that a non-positive dimension raises ValueError.""" - with pytest.raises(ValueError): - GaussianMeasure(0, 1, wrong_dim) - - -# Tests for Lebesgue measure start here - - -def test_lebesgue_input_dim_assignment(input_dim: int): +# Tests for Lebesgue measure +def test_lebesgue_dim_correct(input_dim: int): """Check that dimensions are handled correctly.""" domain1 = (0.0, 1.87) measure11 = LebesgueMeasure(domain=domain1) @@ -100,70 +82,40 @@ def test_lebesgue_input_dim_assignment(input_dim: int): assert measure22.input_dim == input_dim -def test_lebesgue_normalization_value(input_dim: int): +@pytest.mark.parametrize("domain_a", [0, np.full((3,), 0), np.full((13,), 0)]) +@pytest.mark.parametrize("domain_b", [np.full((4,), 1.2), np.full((14,), 1.2)]) +@pytest.mark.parametrize("input_dim", [-10, -2, 0, 2, 12, 122]) +def test_lebesgue_dim_incorrect(domain_a, domain_b, input_dim): + """Check that ValueError is raised if domain limits have mismatching dimensions or + dimension is not positive.""" + with pytest.raises(ValueError): + LebesgueMeasure(domain=(domain_a, domain_b), input_dim=input_dim) + + +def test_lebesgue_normalization(input_dim: int): """Check that normalization constants are handled properly when not equal to one.""" domain = (0, 2) - - # normalized measure = LebesgueMeasure(domain=domain, input_dim=input_dim, normalized=True) + volume = 2**input_dim - assert measure.normalization_constant == 1.0 / volume + assert measure.normalization_constant == 1 / volume + - # not normalized - measure = LebesgueMeasure(domain=domain, input_dim=input_dim, normalized=False) - assert measure.normalization_constant == 1.0 +@pytest.mark.parametrize("domain", [(0, np.Inf), (-np.Inf, 0), (-np.Inf, np.Inf)]) +def test_lebesgue_normalization_raises(domain, input_dim: int): + """Check that exception is raised when normalization is not possible.""" + with pytest.raises(ValueError): + LebesgueMeasure(domain=domain, input_dim=input_dim, normalized=True) -def test_lebesgue_normalization_value_unnormalized(input_dim: int): +def test_lebesgue_unnormalized(input_dim: int): """Check that normalization constants are handled properly when equal to one.""" measure1 = LebesgueMeasure(domain=(0, 1), input_dim=input_dim, normalized=True) measure2 = LebesgueMeasure(domain=(0, 1), input_dim=input_dim, normalized=False) - - assert measure1.normalized - assert not measure2.normalized assert measure1.normalization_constant == measure2.normalization_constant -@pytest.mark.parametrize("wrong_input_dim", [-5, -1, 0]) -def test_lebesgue_non_positive_input_dim_raises(wrong_input_dim): - # non positive input dimenions are not allowed - with pytest.raises(ValueError): - LebesgueMeasure(input_dim=wrong_input_dim, domain=(0, 1)) - - -@pytest.mark.parametrize( - "domain", - [ - (0.0, np.ones(4)), - (np.ones(4), 0.0), - (np.zeros(4), np.ones(3)), - (np.zeros([4, 1]), np.ones(4)), - (np.zeros(4), np.ones([4, 1])), - (np.zeros([4, 1]), np.ones([4, 1])), - ], -) -def test_lebesgue_domain_shape_mismatch_raises(domain): - # left and right bounds of domain are not consistent - with pytest.raises(ValueError): - LebesgueMeasure(domain=domain) - - -@pytest.mark.parametrize( - "input_dim,domain", - [ - (1, (np.zeros(3), np.ones(3))), - (2, (np.zeros(3), np.ones(3))), - (5, (np.zeros(3), np.ones(3))), - ], -) -def test_lebesgue_domain_input_dim_mismatch_raises(input_dim, domain): - # input dimension does not agree with domain shapes - with pytest.raises(ValueError): - LebesgueMeasure(input_dim=input_dim, domain=domain) - - -@pytest.mark.parametrize("domain", [(0, np.Inf), (-np.Inf, 0), (-np.Inf, np.Inf)]) -def test_lebesgue_normalization_raises(domain, input_dim: int): - """Check that exception is raised when normalization is not possible.""" - with pytest.raises(ValueError): - LebesgueMeasure(domain=domain, input_dim=input_dim, normalized=True) +# Tests for all integration measures +def test_density_call(x, measure): + expected_shape = (x.shape[0],) + assert measure(x).shape == expected_shape