diff --git a/botorch/test_functions/__init__.py b/botorch/test_functions/__init__.py index 612d8d946d..136a3e0854 100644 --- a/botorch/test_functions/__init__.py +++ b/botorch/test_functions/__init__.py @@ -54,13 +54,17 @@ Levy, Michalewicz, Powell, + PressureVessel, Rastrigin, Rosenbrock, Shekel, SixHumpCamel, + SpeedReducer, StyblinskiTang, SyntheticTestFunction, + TensionCompressionString, ThreeHumpCamel, + WeldedBeamSO, ) @@ -99,17 +103,21 @@ "OSY", "Penicillin", "Powell", + "PressureVessel", "Rastrigin", "Rosenbrock", "Shekel", "SixHumpCamel", + "SpeedReducer", "SRN", "StyblinskiTang", "SyntheticTestFunction", + "TensionCompressionString", "ThreeHumpCamel", "ToyRobust", "VehicleSafety", "WeldedBeam", + "WeldedBeamSO", "ZDT1", "ZDT2", "ZDT3", diff --git a/botorch/test_functions/multi_objective.py b/botorch/test_functions/multi_objective.py index a46ea5373a..e9255fd76e 100644 --- a/botorch/test_functions/multi_objective.py +++ b/botorch/test_functions/multi_objective.py @@ -1444,7 +1444,10 @@ def evaluate_slack_true(self, X: Tensor) -> Tensor: class WeldedBeam(MultiObjectiveTestProblem, ConstrainedBaseTestProblem): r""" - The Welded Beam test problem. + The Welded Beam multi-objective test problem. Similar to `WeldedBeamSO` in + `botorch.test_function.synthetic`, but with an additional output, somewhat + modified constraints, and a different domain. + Implementation from https://github.com/msu-coinlab/pymoo/blob/master/pymoo/problems/multi/welded_beam.py Note that this implementation assumes minimization, so please choose negate=True. @@ -1462,35 +1465,44 @@ class WeldedBeam(MultiObjectiveTestProblem, ConstrainedBaseTestProblem): _ref_point = [40, 0.015] def evaluate_true(self, X: Tensor) -> Tensor: - f1 = 1.10471 * X[..., 0] ** 2 * X[..., 1] + 0.04811 * X[..., 2] * X[..., 3] * ( - 14.0 + X[..., 1] - ) - f2 = 2.1952 / (X[..., 3] * X[..., 2] ** 3) + # We could do the following, but the constraints are using somewhat + # different numbers (see below). + # f1 = WeldedBeam.evaluate_true(self, X) + x1, x2, x3, x4 = X.unbind(-1) + f1 = 1.10471 * (x1**2) * x2 + 0.04811 * x3 * x4 * (14.0 + x2) + f2 = 2.1952 / (x4 * x3**3) return torch.stack([f1, f2], dim=-1) def evaluate_slack_true(self, X: Tensor) -> Tensor: - P = 6000 - L = 14 - t_max = 13600 - s_max = 30000 - - R = torch.sqrt(0.25 * (X[..., 1] ** 2 + (X[..., 0] + X[..., 2]) ** 2)) - M = P * (L + X[..., 1] / 2) - J = ( - 2 - * math.sqrt(0.5) - * X[..., 0] - * X[..., 1] - * (X[..., 1] ** 2 / 12 + 0.25 * (X[..., 0] + X[..., 2]) ** 2) - ) - t1 = P / (math.sqrt(2) * X[..., 0] * X[..., 1]) + x1, x2, x3, x4 = X.unbind(-1) + P = 6000.0 + L = 14.0 + t_max = 13600.0 + s_max = 30000.0 + + # Ideally, we could just do the following, but the numbers in the + # single-outcome WeldedBeam are different (see below) + # g1_, g2_, g3_, _, _, g6_ = WeldedBeam.evaluate_slack_true(self, X) + # g1 = g1_ / t_max + # g2 = g2_ / s_max + # g3 = 1 / (5 - 0.125) * g3_ + # g4 = 1 / P * g6_ + + R = torch.sqrt(0.25 * (x2**2 + (x1 + x3) ** 2)) + M = P * (L + x2 / 2) + # This `J` is different than the one in [CoelloCoello2002constraint]_ + # by a factor of 2 (sqrt(2) instead of sqrt(0.5)) + J = 2 * math.sqrt(0.5) * x1 * x2 * (x2**2 / 12 + 0.25 * (x1 + x3) ** 2) + t1 = P / (math.sqrt(2) * x1 * x2) t2 = M * R / J - t = torch.sqrt(t1**2 + t2**2 + t1 * t2 * X[..., 1] / R) - s = 6 * P * L / (X[..., 3] * X[..., 2] ** 2) - P_c = 64746.022 * (1 - 0.0282346 * X[..., 2]) * X[..., 2] * X[..., 3] ** 3 - - g1 = (1 / t_max) * (t - t_max) - g2 = (1 / s_max) * (s - s_max) - g3 = (1 / (5 - 0.125)) * (X[..., 0] - X[..., 3]) - g4 = (1 / P) * (P - P_c) - return -torch.stack([g1, g2, g3, g4], dim=-1) + t = torch.sqrt(t1**2 + t1 * t2 * x2 / R + t2**2) + s = 6 * P * L / (x4 * x3**2) + # These numbers are also different from [CoelloCoello2002constraint]_ + P_c = 64746.022 * (1 - 0.0282346 * x3) * x3 * x4**3 + + g1 = (t - t_max) / t_max + g2 = (s - s_max) / s_max + g3 = 1 / (5 - 0.125) * (x1 - x4) + g4 = (P - P_c) / P + + return torch.stack([g1, g2, g3, g4], dim=-1) diff --git a/botorch/test_functions/synthetic.py b/botorch/test_functions/synthetic.py index f44cc1b5df..1e710fd06d 100644 --- a/botorch/test_functions/synthetic.py +++ b/botorch/test_functions/synthetic.py @@ -6,7 +6,32 @@ r""" Synthetic functions for optimization benchmarks. -Reference: https://www.sfu.ca/~ssurjano/optimization.html + +Most test functions (if not indicated otherwise) are taken from +[Bingham2013virtual]_. + + +References: + +.. [Bingham2013virtual] + D. Bingham, S. Surjanovic. Virtual Library of Simulation Experiments. + https://www.sfu.ca/~ssurjano/optimization.html + +.. [CoelloCoello2002constraint] + C. A. Coello Coello and E. Mezura Montes. Constraint-handling in genetic + algorithms through the use of dominance-based tournament selection. + Advanced Engineering Informatics, 16(3):193–203, 2002. + +.. [Hedar2006derivfree] + A.-R. Hedar and M. Fukushima. Derivative-free filter simulated annealing + method for constrained continuous global optimization. Journal of Global + Optimization, 35(4):521–549, 2006. + +.. [Lemonge2010constrained] + A. C. C. Lemonge, H. J. C. Barbosa, C. C. H. Borges, and F. B. dos Santos + Silva. Constrained optimization problems in mechanical engineering design + using a real-coded steady-state genetic algorithm. Mecánica Computacional, + XXIX:9287–9303, 2010. """ from __future__ import annotations @@ -15,7 +40,8 @@ from typing import List, Optional, Tuple import torch -from botorch.test_functions.base import BaseTestProblem +from botorch.test_functions.base import BaseTestProblem, ConstrainedBaseTestProblem +from botorch.test_functions.utils import round_nearest from torch import Tensor @@ -761,3 +787,182 @@ class ThreeHumpCamel(SyntheticTestFunction): def evaluate_true(self, X: Tensor) -> Tensor: x1, x2 = X[..., 0], X[..., 1] return 2.0 * x1**2 - 1.05 * x1**4 + x1**6 / 6.0 + x1 * x2 + x2**2 + + +# ------------ Constrained synthetic test functions ----------- # + + +class PressureVessel(SyntheticTestFunction, ConstrainedBaseTestProblem): + r"""Pressure vessel design problem with constraints. + + The four-dimensional pressure vessel design problem with four black-box + constraints from [CoelloCoello2002constraint]_. + """ + + dim = 4 + num_constraints = 4 + _bounds = [(0.0, 10.0), (0.0, 10.0), (10.0, 50.0), (150.0, 200.0)] + + def evaluate_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4 = X.unbind(-1) + x1 = round_nearest(x1, increment=0.0625, bounds=self._bounds[0]) + x2 = round_nearest(x2, increment=0.0625, bounds=self._bounds[1]) + return ( + 0.6224 * x1 * x3 * x4 + + 1.7781 * x2 * (x3**2) + + 3.1661 * (x1**2) * x4 + + 19.84 * (x1**2) * x3 + ) + + def evaluate_slack_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4 = X.unbind(-1) + return -torch.stack( + [ + -x1 + 0.0193 * x3, + -x2 + 0.00954 * x3, + -math.pi * (x3**2) * x4 - (4 / 3) * math.pi * (x3**3) + 1296000.0, + x4 - 240.0, + ], + dim=-1, + ) + + +class WeldedBeamSO(SyntheticTestFunction, ConstrainedBaseTestProblem): + r"""Welded beam design problem with constraints (single-outcome). + + The four-dimensional welded beam design proble problem with six + black-box constraints from [CoelloCoello2002constraint]_. + + For a (somewhat modified) multi-objective version, see + `botorch.test_functions.multi_objective.WeldedBeam`. + """ + + dim = 4 + num_constraints = 6 + _bounds = [(0.125, 10.0), (0.1, 10.0), (0.1, 10.0), (0.1, 10.0)] + + def evaluate_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4 = X.unbind(-1) + return 1.10471 * (x1**2) * x2 + 0.04811 * x3 * x4 * (14.0 + x2) + + def evaluate_slack_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4 = X.unbind(-1) + P = 6000.0 + L = 14.0 + E = 30e6 + G = 12e6 + t_max = 13600.0 + s_max = 30000.0 + d_max = 0.25 + + M = P * (L + x2 / 2) + R = torch.sqrt(0.25 * (x2**2 + (x1 + x3) ** 2)) + J = 2 * math.sqrt(2) * x1 * x2 * (x2**2 / 12 + 0.25 * (x1 + x3) ** 2) + P_c = ( + 4.013 + * E + * x3 + * (x4**3) + * 6 + / (L**2) + * (1 - 0.25 * x3 * math.sqrt(E / G) / L) + ) + t1 = P / (math.sqrt(2) * x1 * x2) + t2 = M * R / J + t = torch.sqrt(t1**2 + t1 * t2 * x2 / R + t2**2) + s = 6 * P * L / (x4 * x3**2) + d = 4 * P * L**3 / (E * x3**3 * x4) + + g1 = t - t_max + g2 = s - s_max + g3 = x1 - x4 + g4 = 0.10471 * x1**2 + 0.04811 * x3 * x4 * (14.0 + x2) - 5.0 + g5 = d - d_max + g6 = P - P_c + + return -torch.stack([g1, g2, g3, g4, g5, g6], dim=-1) + + +class TensionCompressionString(SyntheticTestFunction, ConstrainedBaseTestProblem): + r"""Tension compression string optimization problem with constraints. + + The three-dimensional tension compression string optimization problem with + four black-box constraints from [Hedar2006derivfree]_. + """ + + dim = 3 + num_constraints = 4 + _bounds = [(0.01, 1.0), (0.01, 1.0), (0.01, 20.0)] + + def evaluate_true(self, X: Tensor) -> Tensor: + x1, x2, x3 = X.unbind(-1) + return (x1**2) * x2 * (x3 + 2) + + def evaluate_slack_true(self, X: Tensor) -> Tensor: + x1, x2, x3 = X.unbind(-1) + constraints = torch.stack( + [ + 1 - (x2**3) * x3 / (71785 * (x1**4)), + (4 * (x2**2) - x1 * x2) / (12566 * (x1**3) * (x2 - x1)) + + 1 / (5108 * (x1**2)) + - 1, + 1 - 140.45 * x1 / (x3 * (x2**2)), + (x1 + x2) / 1.5 - 1, + ], + dim=-1, + ) + return -constraints.clamp_max(100) + + +class SpeedReducer(SyntheticTestFunction, ConstrainedBaseTestProblem): + r"""Speed Reducer design problem with constraints. + + The seven-dimensional speed reducer design problem with eleven black-box + constraints from [Lemonge2010constrained]_. + """ + + dim = 7 + num_constraints = 11 + _bounds = [ + (2.6, 3.6), + (0.7, 0.8), + (17.0, 28.0), + (7.3, 8.3), + (7.8, 8.3), + (2.9, 3.9), + (5.0, 5.5), + ] + + def evaluate_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4, x5, x6, x7 = X.unbind(-1) + return ( + 0.7854 * x1 * (x2**2) * (3.3333 * (x3**2) + 14.9334 * x3 - 43.0934) + + -1.508 * x1 * (x6**2 + x7**2) + + 7.4777 * (x6**3 + x7**3) + + 0.7854 * (x4 * (x6**2) + x5 * (x7**2)) + ) + + def evaluate_slack_true(self, X: Tensor) -> Tensor: + x1, x2, x3, x4, x5, x6, x7 = X.unbind(-1) + return -torch.stack( + [ + 27.0 * (1 / x1) * (1 / (x2**2)) * (1 / x3) - 1, + 397.5 * (1 / x1) * (1 / (x2**2)) * (1 / (x3**2)) - 1, + 1.93 * (1 / x2) * (1 / x3) * (x4**3) * (1 / (x6**4)) - 1, + 1.93 * (1 / x2) * (1 / x3) * (x5**3) * (1 / (x7**4)) - 1, + 1 + / (0.1 * (x6**3)) + * torch.sqrt((745 * x4 / (x2 * x3)) ** 2 + 16.9 * 1e6) + - 1100, + 1 + / (0.1 * (x7**3)) + * torch.sqrt((745 * x5 / (x2 * x3)) ** 2 + 157.5 * 1e6) + - 850, + x2 * x3 - 40, + 5 - x1 / x2, + x1 / x2 - 12, + (1.5 * x6 + 1.9) / x4 - 1, + (1.1 * x7 + 1.9) / x5 - 1, + ], + dim=-1, + ) diff --git a/botorch/test_functions/utils.py b/botorch/test_functions/utils.py new file mode 100644 index 0000000000..969524822d --- /dev/null +++ b/botorch/test_functions/utils.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from __future__ import annotations + +from typing import Optional, Tuple + +import torch + +from torch import Tensor + + +def round_nearest( + X: Tensor, increment: float, bounds: Optional[Tuple[float, float]] +) -> Tensor: + r"""Rounds the input tensor to the nearest multiple of `increment`. + + Args: + X: The input to be rounded. + increment: The increment to round to. + bounds: An optional tuple of two floats representing the lower and upper + bounds on `X`. If provided, this will round to the nearest multiple + of `increment` that lies within the bounds. + + Returns: + The rounded input. + """ + X_round = torch.round(X / increment) * increment + if bounds is not None: + X_round = torch.where(X_round < bounds[0], X_round + increment, X_round) + X_round = torch.where(X_round > bounds[1], X_round - increment, X_round) + return X_round diff --git a/botorch/utils/testing.py b/botorch/utils/testing.py index 0754378f8c..34947d0857 100644 --- a/botorch/utils/testing.py +++ b/botorch/utils/testing.py @@ -8,6 +8,7 @@ import math import warnings +from abc import abstractproperty from collections import OrderedDict from typing import Any, List, Optional, Tuple, Union from unittest import TestCase @@ -93,10 +94,7 @@ def assertAllClose( ) -class BaseTestProblemBaseTestCase: - - functions: List[BaseTestProblem] - +class BaseTestProblemTestCaseMixIn: def test_forward(self): for dtype in (torch.float, torch.double): for batch_shape in (torch.Size(), torch.Size([2]), torch.Size([2, 3])): @@ -113,8 +111,14 @@ def test_forward(self): ) self.assertEqual(res.shape, batch_shape + tail_shape) + @abstractproperty + def functions(self) -> List[BaseTestProblem]: + # The functions that should be tested. Typically defined as a class + # attribute on the test case subclassing this class. + pass # pragma: no cover + -class SyntheticTestFunctionBaseTestCase(BaseTestProblemBaseTestCase): +class SyntheticTestFunctionTestCaseMixin: def test_optimal_value(self): for dtype in (torch.float, torch.double): for f in self.functions: @@ -143,6 +147,52 @@ def test_optimizer(self): self.assertLess(grad.abs().max().item(), 1e-3) +class MultiObjectiveTestProblemTestCaseMixin: + def test_attributes(self): + for f in self.functions: + self.assertTrue(hasattr(f, "dim")) + self.assertTrue(hasattr(f, "num_objectives")) + self.assertEqual(f.bounds.shape, torch.Size([2, f.dim])) + + def test_max_hv(self): + for dtype in (torch.float, torch.double): + for f in self.functions: + f.to(device=self.device, dtype=dtype) + if not hasattr(f, "_max_hv"): + with self.assertRaises(NotImplementedError): + f.max_hv + else: + self.assertEqual(f.max_hv, f._max_hv) + + def test_ref_point(self): + for dtype in (torch.float, torch.double): + for f in self.functions: + f.to(dtype=dtype, device=self.device) + self.assertTrue( + torch.allclose( + f.ref_point, + torch.tensor(f._ref_point, dtype=dtype, device=self.device), + ) + ) + + +class ConstrainedTestProblemTestCaseMixin: + def test_num_constraints(self): + for f in self.functions: + self.assertTrue(hasattr(f, "num_constraints")) + + def test_evaluate_slack_true(self): + for dtype in (torch.float, torch.double): + for f in self.functions: + f.to(device=self.device, dtype=dtype) + X = unnormalize( + torch.rand(1, f.dim, device=self.device, dtype=dtype), + bounds=f.bounds, + ) + slack = f.evaluate_slack_true(X) + self.assertEqual(slack.shape, torch.Size([1, f.num_constraints])) + + class MockPosterior(Posterior): r"""Mock object that implements dummy methods and feeds through specified outputs""" @@ -368,51 +418,3 @@ def _get_test_posterior( covar = covar + torch.diag_embed(flat_diag) mtmvn = MultitaskMultivariateNormal(mean, covar, interleaved=interleaved) return GPyTorchPosterior(mtmvn) - - -class MultiObjectiveTestProblemBaseTestCase(BaseTestProblemBaseTestCase): - def test_attributes(self): - for f in self.functions: - self.assertTrue(hasattr(f, "dim")) - self.assertTrue(hasattr(f, "num_objectives")) - self.assertEqual(f.bounds.shape, torch.Size([2, f.dim])) - - def test_max_hv(self): - for dtype in (torch.float, torch.double): - for f in self.functions: - f.to(device=self.device, dtype=dtype) - if not hasattr(f, "_max_hv"): - with self.assertRaises(NotImplementedError): - f.max_hv - else: - self.assertEqual(f.max_hv, f._max_hv) - - def test_ref_point(self): - for dtype in (torch.float, torch.double): - for f in self.functions: - f.to(dtype=dtype, device=self.device) - self.assertTrue( - torch.allclose( - f.ref_point, - torch.tensor(f._ref_point, dtype=dtype, device=self.device), - ) - ) - - -class ConstrainedMultiObjectiveTestProblemBaseTestCase( - MultiObjectiveTestProblemBaseTestCase -): - def test_num_constraints(self): - for f in self.functions: - self.assertTrue(hasattr(f, "num_constraints")) - - def test_evaluate_slack_true(self): - for dtype in (torch.float, torch.double): - for f in self.functions: - f.to(device=self.device, dtype=dtype) - X = unnormalize( - torch.rand(1, f.dim, device=self.device, dtype=dtype), - bounds=f.bounds, - ) - slack = f.evaluate_slack_true(X) - self.assertEqual(slack.shape, torch.Size([1, f.num_constraints])) diff --git a/sphinx/source/test_functions.rst b/sphinx/source/test_functions.rst index cf9e324b37..bfde346766 100644 --- a/sphinx/source/test_functions.rst +++ b/sphinx/source/test_functions.rst @@ -35,3 +35,8 @@ Sensitivity Analysis Test Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: botorch.test_functions.sensitivity_analysis :members: + +Utilities For Test Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. automodule:: botorch.test_functions.utils + :members: diff --git a/test/test_functions/test_multi_fidelity.py b/test/test_functions/test_multi_fidelity.py index 68482e6de7..d1e8257c57 100644 --- a/test/test_functions/test_multi_fidelity.py +++ b/test/test_functions/test_multi_fidelity.py @@ -9,10 +9,16 @@ AugmentedHartmann, AugmentedRosenbrock, ) -from botorch.utils.testing import BotorchTestCase, SyntheticTestFunctionBaseTestCase +from botorch.utils.testing import ( + BaseTestProblemTestCaseMixIn, + BotorchTestCase, + SyntheticTestFunctionTestCaseMixin, +) -class TestAugmentedBranin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestAugmentedBranin( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ AugmentedBranin(), @@ -21,7 +27,9 @@ class TestAugmentedBranin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestAugmentedHartmann(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestAugmentedHartmann( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ AugmentedHartmann(), @@ -30,7 +38,9 @@ class TestAugmentedHartmann(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestAugmentedRosenbrock(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestAugmentedRosenbrock( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ AugmentedRosenbrock(), diff --git a/test/test_functions/test_multi_objective.py b/test/test_functions/test_multi_objective.py index 4b3ac32f50..6a5295b4e5 100644 --- a/test/test_functions/test_multi_objective.py +++ b/test/test_functions/test_multi_objective.py @@ -40,9 +40,10 @@ ZDT3, ) from botorch.utils.testing import ( + BaseTestProblemTestCaseMixIn, BotorchTestCase, - ConstrainedMultiObjectiveTestProblemBaseTestCase, - MultiObjectiveTestProblemBaseTestCase, + ConstrainedTestProblemTestCaseMixin, + MultiObjectiveTestProblemTestCaseMixin, ) @@ -74,7 +75,11 @@ def test_base_mo_problem(self): f.gen_pareto_front(1) -class TestBraninCurrin(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestBraninCurrin( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [BraninCurrin()] def test_init(self): @@ -83,7 +88,11 @@ def test_init(self): self.assertEqual(f.dim, 2) -class TestDH(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestDH( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [DH1(dim=2), DH2(dim=3), DH3(dim=4), DH4(dim=5)] dims = [2, 3, 4, 5] bounds = [ @@ -118,7 +127,11 @@ def test_function_values(self): self.assertAllClose(actual, expected) -class TestDTLZ(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestDTLZ( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [ DTLZ1(dim=5, num_objectives=2), DTLZ2(dim=5, num_objectives=2), @@ -180,7 +193,11 @@ def test_gen_pareto_front(self): ) -class TestGMM(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestGMM( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [GMM(num_objectives=4)] def test_init(self): @@ -226,7 +243,12 @@ def test_result(self): ) -class TestMW7(ConstrainedMultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestMW7( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): functions = [MW7(dim=3)] def test_init(self): @@ -237,7 +259,11 @@ def test_init(self): self.assertEqual(f.dim, 3) -class TestZDT(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestZDT( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [ ZDT1(dim=3, num_objectives=2), ZDT2(dim=3, num_objectives=2), @@ -304,27 +330,119 @@ def test_gen_pareto_front(self): ) -class TestMultiObjectiveProblems( - MultiObjectiveTestProblemBaseTestCase, BotorchTestCase +# ------------------ Unconstrained Multi-objective test problems ------------------ # + + +class TestCarSideImpact( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): + + functions = [CarSideImpact()] + + +class TestPenicillin( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, ): - functions = [CarSideImpact(), Penicillin(), ToyRobust(), VehicleSafety()] + functions = [Penicillin()] -class TestConstrainedMultiObjectiveProblems( - ConstrainedMultiObjectiveTestProblemBaseTestCase, BotorchTestCase +class TestToyRobust( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): + functions = [ToyRobust()] + + +class TestVehicleSafety( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): + functions = [VehicleSafety()] + + +# ------------------ Constrained Multi-objective test problems ------------------ # + + +class TestBNH( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [BNH()] + + +class TestSRN( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [SRN()] + + +class TestCONSTR( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [CONSTR()] + + +class TestConstrainedBraninCurrin( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, ): functions = [ - BNH(), - SRN(), - CONSTR(), ConstrainedBraninCurrin(), - C2DTLZ2(dim=3, num_objectives=2), - DiscBrake(), - WeldedBeam(), - OSY(), ] - def test_c2dtlz2_batch_exception(self): + +class TestC2DTLZ2( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [C2DTLZ2(dim=3, num_objectives=2)] + + def test_batch_exception(self): f = C2DTLZ2(dim=3, num_objectives=2) with self.assertRaises(NotImplementedError): f.evaluate_slack_true(torch.empty(1, 1, 3)) + + +class TestDiscBrake( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [DiscBrake()] + + +class TestWeldedBeam( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [WeldedBeam()] + + +class TestOSY( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, + ConstrainedTestProblemTestCaseMixin, +): + functions = [OSY()] diff --git a/test/test_functions/test_multi_objective_multi_fidelity.py b/test/test_functions/test_multi_objective_multi_fidelity.py index 7563803a99..b4ceae1c6e 100644 --- a/test/test_functions/test_multi_objective_multi_fidelity.py +++ b/test/test_functions/test_multi_objective_multi_fidelity.py @@ -8,10 +8,18 @@ MOMFBraninCurrin, MOMFPark, ) -from botorch.utils.testing import BotorchTestCase, MultiObjectiveTestProblemBaseTestCase +from botorch.utils.testing import ( + BaseTestProblemTestCaseMixIn, + BotorchTestCase, + MultiObjectiveTestProblemTestCaseMixin, +) -class TestMOMFBraninCurrin(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestMOMFBraninCurrin( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [MOMFBraninCurrin()] bounds = [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]] @@ -24,7 +32,11 @@ def test_init(self): ) -class TestMOMFPark(MultiObjectiveTestProblemBaseTestCase, BotorchTestCase): +class TestMOMFPark( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + MultiObjectiveTestProblemTestCaseMixin, +): functions = [MOMFPark()] bounds = [[0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0, 1.0]] diff --git a/test/test_functions/test_synthetic.py b/test/test_functions/test_synthetic.py index 457d0859d7..4e8198f6cf 100644 --- a/test/test_functions/test_synthetic.py +++ b/test/test_functions/test_synthetic.py @@ -21,15 +21,24 @@ Levy, Michalewicz, Powell, + PressureVessel, Rastrigin, Rosenbrock, Shekel, SixHumpCamel, + SpeedReducer, StyblinskiTang, SyntheticTestFunction, + TensionCompressionString, ThreeHumpCamel, + WeldedBeamSO, +) +from botorch.utils.testing import ( + BaseTestProblemTestCaseMixIn, + BotorchTestCase, + ConstrainedTestProblemTestCaseMixin, + SyntheticTestFunctionTestCaseMixin, ) -from botorch.utils.testing import BotorchTestCase, SyntheticTestFunctionBaseTestCase from torch import Tensor @@ -46,7 +55,7 @@ class DummySyntheticTestFunctionWithOptimizers(DummySyntheticTestFunction): _optimizers = [(0, 0)] -class TestSyntheticTestFunction(BotorchTestCase): +class TestCustomBounds(BotorchTestCase): functions_with_custom_bounds = [ # Function name and the default dimension. (Ackley, 2), (Beale, 2), @@ -100,37 +109,51 @@ def test_custom_bounds(self): self.assertTrue(torch.allclose(func.bounds, bounds_tensor)) -class TestAckley(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestAckley( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Ackley(), Ackley(negate=True), Ackley(noise_std=0.1), Ackley(dim=3)] -class TestBeale(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestBeale( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Beale(), Beale(negate=True), Beale(noise_std=0.1)] -class TestBranin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestBranin( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Branin(), Branin(negate=True), Branin(noise_std=0.1)] -class TestBukin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestBukin( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Bukin(), Bukin(negate=True), Bukin(noise_std=0.1)] -class TestCosine8(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestCosine8( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Cosine8(), Cosine8(negate=True), Cosine8(noise_std=0.1)] -class TestDropWave(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestDropWave( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [DropWave(), DropWave(negate=True), DropWave(noise_std=0.1)] -class TestDixonPrice(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestDixonPrice( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ DixonPrice(), @@ -140,12 +163,16 @@ class TestDixonPrice(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestEggHolder(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestEggHolder( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [EggHolder(), EggHolder(negate=True), EggHolder(noise_std=0.1)] -class TestGriewank(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestGriewank( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Griewank(), @@ -155,7 +182,9 @@ class TestGriewank(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestHartmann(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestHartmann( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Hartmann(), @@ -174,12 +203,16 @@ def test_dimension(self): Hartmann(dim=2) -class TestHolderTable(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestHolderTable( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [HolderTable(), HolderTable(negate=True), HolderTable(noise_std=0.1)] -class TestLevy(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestLevy( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Levy(), @@ -191,7 +224,9 @@ class TestLevy(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestMichalewicz(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestMichalewicz( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Michalewicz(), @@ -206,12 +241,16 @@ class TestMichalewicz(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestPowell(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestPowell( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Powell(), Powell(negate=True), Powell(noise_std=0.1)] -class TestRastrigin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestRastrigin( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Rastrigin(), @@ -223,7 +262,9 @@ class TestRastrigin(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestRosenbrock(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestRosenbrock( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ Rosenbrock(), @@ -235,17 +276,23 @@ class TestRosenbrock(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestShekel(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestShekel( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [Shekel(), Shekel(negate=True), Shekel(noise_std=0.1)] -class TestSixHumpCamel(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestSixHumpCamel( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [SixHumpCamel(), SixHumpCamel(negate=True), SixHumpCamel(noise_std=0.1)] -class TestStyblinskiTang(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestStyblinskiTang( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ StyblinskiTang(), @@ -257,10 +304,51 @@ class TestStyblinskiTang(SyntheticTestFunctionBaseTestCase, BotorchTestCase): ] -class TestThreeHumpCamel(SyntheticTestFunctionBaseTestCase, BotorchTestCase): +class TestThreeHumpCamel( + BotorchTestCase, BaseTestProblemTestCaseMixIn, SyntheticTestFunctionTestCaseMixin +): functions = [ ThreeHumpCamel(), ThreeHumpCamel(negate=True), ThreeHumpCamel(noise_std=0.1), ] + + +# ------------------ Constrained synthetic test problems ------------------ # + + +class TestPressureVessel( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + ConstrainedTestProblemTestCaseMixin, +): + + functions = [PressureVessel()] + + +class TestSpeedReducer( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + ConstrainedTestProblemTestCaseMixin, +): + + functions = [SpeedReducer()] + + +class TestTensionCompressionString( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + ConstrainedTestProblemTestCaseMixin, +): + + functions = [TensionCompressionString()] + + +class TestWeldedBeamSO( + BotorchTestCase, + BaseTestProblemTestCaseMixIn, + ConstrainedTestProblemTestCaseMixin, +): + + functions = [WeldedBeamSO()]