From 338d74f09f8a8ceffac24e69c8595dec6cbe0cd2 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Wed, 18 Sep 2024 09:14:52 +0100 Subject: [PATCH 1/4] Bump version to 4.1.0 --- CITATION.cff | 4 ++-- trieste/VERSION | 2 +- versions.json | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index b743b07c9..33e811241 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -30,6 +30,6 @@ authors: - family-names: "Picheny" given-names: "Victor" title: "Trieste" -version: 4.0.1 -date-released: 2024-09-10 +version: 4.1.0 +date-released: 2024-09-18 url: "https://github.com/secondmind-labs/trieste" diff --git a/trieste/VERSION b/trieste/VERSION index 1454f6ed4..ee74734aa 100644 --- a/trieste/VERSION +++ b/trieste/VERSION @@ -1 +1 @@ -4.0.1 +4.1.0 diff --git a/versions.json b/versions.json index 0f6c25160..20612fa66 100644 --- a/versions.json +++ b/versions.json @@ -3,6 +3,10 @@ "version": "develop", "url": "https://secondmind-labs.github.io/trieste/develop/" }, + { + "version": "4.1.0", + "url": "https://secondmind-labs.github.io/trieste/4.1.0/" + }, { "version": "4.0.1", "url": "https://secondmind-labs.github.io/trieste/4.0.1/" From 6ff43c0c0fd1555bc549d3da6906b1e33577b239 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Thu, 19 Sep 2024 12:35:13 +0100 Subject: [PATCH 2/4] Support vectorization in sample_from_space --- tests/unit/acquisition/test_optimizer.py | 33 +++++++++++++-- trieste/acquisition/optimizer.py | 51 +++++++++++++++++++----- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/tests/unit/acquisition/test_optimizer.py b/tests/unit/acquisition/test_optimizer.py index 64b716e9d..368122925 100644 --- a/tests/unit/acquisition/test_optimizer.py +++ b/tests/unit/acquisition/test_optimizer.py @@ -1017,10 +1017,37 @@ def test_sample_from_space(num_samples: int, batch_size: Optional[int]) -> None: assert len(set(float(x) for batch in batches for x in batch)) == num_samples -@pytest.mark.parametrize("num_samples,batch_size", [(0, None), (-5, None), (5, 0), (5, -5)]) -def test_sample_from_space_raises(num_samples: int, batch_size: Optional[int]) -> None: +@pytest.mark.parametrize( + "space", [Box([0], [1]), TaggedMultiSearchSpace([Box([0], [1]), Box([0], [1])])] +) +def test_sample_from_space_vectorization(space: SearchSpace) -> None: + batches = list(sample_from_space(10, vectorization=4)(space)) + assert len(batches) == 1 + assert batches[0].shape == [10, 4, 1] + assert 0 <= tf.reduce_min(batches[0]) <= tf.reduce_max(batches[0]) <= 1 + # check that the vector batches aren't all the same + assert tf.reduce_any((batches[0] - batches[0][:, 0:1, :]) != 0) + + +@pytest.mark.parametrize( + "num_samples,batch_size,vectorization", + [(0, None, 1), (-5, None, 1), (5, 0, 1), (5, -5, 1), (5, 5, 0), (5, 5, -1)], +) +def test_sample_from_space_raises( + num_samples: int, batch_size: Optional[int], vectorization: int +) -> None: with pytest.raises(ValueError): - sample_from_space(num_samples=num_samples, batch_size=batch_size) + sample_from_space( + num_samples=num_samples, batch_size=batch_size, vectorization=vectorization + ) + + +def test_sample_from_space_vectorization_raises_with_invalid_space() -> None: + # vectorisation of 3 not possible with 2 subspace multisearchspace + space = TaggedMultiSearchSpace([Box([0], [1]), Box([0], [1])]) + sampler = sample_from_space(10, vectorization=3) + with pytest.raises(tf.errors.InvalidArgumentError): + list(sampler(space)) def test_optimize_continuous_raises_for_insufficient_starting_points() -> None: diff --git a/trieste/acquisition/optimizer.py b/trieste/acquisition/optimizer.py index 0e9e45a70..fdd8c01fa 100644 --- a/trieste/acquisition/optimizer.py +++ b/trieste/acquisition/optimizer.py @@ -193,10 +193,18 @@ def sampler(space: SearchSpace) -> Iterable[TensorType]: """ -def sample_from_space(num_samples: int, batch_size: Optional[int] = None) -> InitialPointSampler: +def sample_from_space( + num_samples: int, + batch_size: Optional[int] = None, + vectorization: int = 1, +) -> InitialPointSampler: """ - An initial point sampler that returns `num_samples` points. If `batch_size` is specified, - then these are returned in batches of that size, to preserve memory usage. + An initial point sampler that just samples from the search pace. + + :param num_samples: Number of samples to return. + :param batch_size: If specified, points are return in batches of this size, + to preserve memory usage. + :param vectorization: Vectorization of the target function. """ if num_samples <= 0: raise ValueError(f"num_samples must be positive, got {num_samples}") @@ -204,11 +212,34 @@ def sample_from_space(num_samples: int, batch_size: Optional[int] = None) -> Ini if isinstance(batch_size, int) and batch_size <= 0: raise ValueError(f"batch_size must be positive, got {batch_size}") + if vectorization <= 0: + raise ValueError(f"vectorization must be positive, got {vectorization}") + batch_size_int = batch_size or num_samples def sampler(space: SearchSpace) -> Iterable[TensorType]: + + # generate additional points for each vectorization (rather than just replicating them) + if isinstance(space, TaggedMultiSearchSpace): + remainder = vectorization % len(space.subspace_tags) + tf.debugging.assert_equal( + remainder, + 0, + message=( + f"The vectorization of the target function {vectorization} must be a" + f"multiple of the batch shape of initial samples " + f"{len(space.subspace_tags)}." + ), + ) + multiple = vectorization // len(space.subspace_tags) + else: + multiple = vectorization + for offset in range(0, num_samples, batch_size_int): - yield space.sample(min(num_samples - offset, batch_size_int)) + num_batch_samples = min(num_samples - offset, batch_size_int) + candidates = space.sample(num_batch_samples * multiple) + candidates = tf.reshape(candidates, [num_batch_samples, vectorization, -1]) + yield candidates return sampler @@ -363,12 +394,6 @@ def generate_continuous_optimizer( if num_recovery_runs < 0: raise ValueError(f"num_recovery_runs must be zero or greater, got {num_recovery_runs}") - initial_sampler = ( - sample_from_space(num_initial_samples) - if not callable(num_initial_samples) - else num_initial_samples - ) - def optimize_continuous( space: Box | CollectionSearchSpace, target_func: Union[AcquisitionFunction, Tuple[AcquisitionFunction, int]], @@ -400,6 +425,12 @@ def optimize_continuous( if V <= 0: raise ValueError(f"vectorization must be positive, got {V}") + initial_sampler = ( + sample_from_space(num_initial_samples, vectorization=V) + if not callable(num_initial_samples) + else num_initial_samples + ) + initial_points = generate_initial_points( num_optimization_runs, initial_sampler, space, target_func, V ) # [num_optimization_runs,V,D] From 61ee07de85f420f72e7dd0fb9c7124a4c061f282 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Thu, 19 Sep 2024 13:37:34 +0100 Subject: [PATCH 3/4] Increase num_initial_samples in broken test --- tests/unit/acquisition/test_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/acquisition/test_optimizer.py b/tests/unit/acquisition/test_optimizer.py index 368122925..f2d17461a 100644 --- a/tests/unit/acquisition/test_optimizer.py +++ b/tests/unit/acquisition/test_optimizer.py @@ -802,7 +802,7 @@ def target_function(x: TensorType) -> TensorType: # [N,V,D] -> [N, V] return tf.concat(individual_func, axis=-1) # vectorize by repeating same function optimizer = batchify_vectorize( - generate_continuous_optimizer(num_initial_samples=1_000, num_optimization_runs=10), + generate_continuous_optimizer(num_initial_samples=20_000, num_optimization_runs=10), batch_size=vectorization, ) maximizer = optimizer(search_space, target_function) From b7acaffc3ca0deba961d524cfe0e49625fd8fbf6 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Fri, 20 Sep 2024 09:11:48 +0100 Subject: [PATCH 4/4] random_seed --- tests/integration/test_mixed_space_bayesian_optimization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_mixed_space_bayesian_optimization.py b/tests/integration/test_mixed_space_bayesian_optimization.py index ce80c030c..16bc91a9a 100644 --- a/tests/integration/test_mixed_space_bayesian_optimization.py +++ b/tests/integration/test_mixed_space_bayesian_optimization.py @@ -253,6 +253,7 @@ def objective(x: TensorType) -> TensorType: ) +@random_seed def _get_categorical_problem() -> SingleObjectiveTestProblem[TaggedProductSearchSpace]: # a categorical scaled branin problem with 6 categories mapping to 3 random points # plus the 3 minimizer points (to guarantee that the minimum is present)