Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cbranincurrin synthetic test function for cmoo #692

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
8 changes: 8 additions & 0 deletions docs/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,11 @@ @InProceedings{moss2023IPA
booktitle = {Proceedings of the Twenty-Fith International Conference on Artificial Intelligence and Statistics},
year = {2023},
}

@article{belakaria2019max,
title={Max-value entropy search for multi-objective bayesian optimization},
author={Belakaria, Syrine and Deshwal, Aryan and Doppa, Janardhan Rao},
journal={Advances in Neural Information Processing Systems},
volume={32},
year={2019}
}
52 changes: 49 additions & 3 deletions tests/unit/objectives/test_multi_objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Callable
from typing import Callable, Union

import numpy.testing as npt
import pytest
import tensorflow as tf

from tests.util.misc import TF_DEBUGGING_ERROR_TYPES
from trieste.objectives.multi_objectives import DTLZ1, DTLZ2, VLMOP2, MultiObjectiveTestProblem
from trieste.objectives.multi_objectives import (
DTLZ1,
DTLZ2,
VLMOP2,
ConstrainedBraninCurrin,
ConstrainedMultiObjectiveTestProblem,
MultiObjectiveTestProblem,
)
from trieste.types import TensorType


Expand Down Expand Up @@ -107,6 +114,40 @@ def test_dtlz2_has_expected_output(
npt.assert_allclose(f(test_x), expected, rtol=1e-4)


@pytest.mark.parametrize(
"test_x, expected_obj, expected_con",
[
(
tf.constant([[0.0, 0.0]]),
tf.constant([[308.12909601160663, 3.0]]),
tf.constant([[62.5]]),
),
(
tf.constant([[0.5, 1.0]]),
tf.constant([[150.45202034083485, 4.609388478538837]]),
tf.constant([[6.25]]),
),
(
tf.constant([[[0.5, 1.0]], [[0.0, 0.0]]]),
tf.constant([[[150.45202034083485, 4.609388478538837]], [[308.12909601160663, 3.0]]]),
tf.constant([[[6.25]], [[62.5]]]),
),
(
tf.constant([[0.5, 1.0], [0.0, 0.0]]),
tf.constant([[150.45202034083485, 4.609388478538837], [308.12909601160663, 3.0]]),
tf.constant([[6.25], [62.5]]),
),
],
)
def test_constrainedbranincurrin_has_expected_output(
test_x: TensorType, expected_obj: TensorType, expected_con: TensorType
) -> None:
f = ConstrainedBraninCurrin().objective
c = ConstrainedBraninCurrin().constraint
npt.assert_allclose(f(test_x), expected_obj, rtol=1e-5)
npt.assert_allclose(c(test_x), expected_con, rtol=1e-5)


@pytest.mark.parametrize(
"obj_type, input_dim, num_obj, gen_pf_num",
[
Expand Down Expand Up @@ -137,13 +178,18 @@ def test_gen_pareto_front_is_equal_to_math_defined(
(VLMOP2(2), tf.constant([[0.4, 0.2, 0.5]])),
(DTLZ1(3, 2), tf.constant([[0.3, 0.1]])),
(DTLZ2(5, 2), tf.constant([[0.3, 0.1]])),
(ConstrainedBraninCurrin(), tf.constant([[0.3, 0.2, 0.1]])),
],
)
def test_func_raises_specified_input_dim_not_align_with_actual_input_dim(
obj_inst: MultiObjectiveTestProblem, actual_x: TensorType
obj_inst: Union[MultiObjectiveTestProblem, ConstrainedMultiObjectiveTestProblem],
actual_x: TensorType,
) -> None:
with pytest.raises(TF_DEBUGGING_ERROR_TYPES):
obj_inst.objective(actual_x)
if isinstance(obj_inst, ConstrainedMultiObjectiveTestProblem):
with pytest.raises(TF_DEBUGGING_ERROR_TYPES):
obj_inst.constraint(actual_x)


@pytest.mark.parametrize(
TsingQAQ marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
76 changes: 75 additions & 1 deletion trieste/objectives/multi_objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
import math
from dataclasses import dataclass
from functools import partial
from typing import Callable

import tensorflow as tf
from typing_extensions import Protocol

from ..space import Box
from ..types import TensorType
from .single_objectives import ObjectiveTestProblem
from .single_objectives import ObjectiveTestProblem, branin


class GenParetoOptimalPoints(Protocol):
Expand Down Expand Up @@ -236,3 +237,76 @@ def gen_pareto_optimal_points(n: int, seed: int | None = None) -> TensorType:
search_space=Box([0.0], [1.0]) ** d,
gen_pareto_optimal_points=gen_pareto_optimal_points,
)


@dataclass(frozen=True)
class ConstrainedMultiObjectiveTestProblem(MultiObjectiveTestProblem):
TsingQAQ marked this conversation as resolved.
Show resolved Hide resolved
"""
Convenience container class for synthetic constrained multi-objective test functions, containing
a generator for the pareto optimal points, which can be used as a reference of performance
measure of certain multi-objective optimization algorithms.
TsingQAQ marked this conversation as resolved.
Show resolved Hide resolved
"""

constraint: Callable[[TensorType], TensorType]
"""The synthetic test function's constraints"""


def ConstrainedBraninCurrin() -> ConstrainedMultiObjectiveTestProblem:
"""
The ConstrainedBraninCurrin problem, typically evaluated over :math:`[0, 1]^2`.
See :cite:`belakaria2019max` and :cite:`daulton2020differentiable`
(the latter for adding the constraint) for details.

:return: The problem specification.
"""

def gen_pareto_optimal_points(n: int, seed: int | None = None) -> TensorType:
return tf.zeros(shape=0)

def evaluate_slack_true(x: TensorType) -> TensorType:
TsingQAQ marked this conversation as resolved.
Show resolved Hide resolved
"""
The constraint of branincurrin problem, < 0 is feasible

:param x: The points at which to evaluate the function, with shape [..., d].
:raise ValueError (or InvalidArgumentError): If ``x`` has an invalid shape.
"""
x = x * (
tf.constant([10.0, 15.0], dtype=x.dtype) - tf.constant([-5.0, 0.0], dtype=x.dtype)
) + tf.constant([-5.0, 0.0], dtype=x.dtype)
return (x[..., :1] - 2.5) ** 2 + (x[..., 1:] - 7.5) ** 2 - 50

return ConstrainedMultiObjectiveTestProblem(
name="ConstrainedBraninCurrin",
objective=branin_currin,
constraint=evaluate_slack_true,
search_space=Box([0.0], [1.0]) ** 2,
gen_pareto_optimal_points=gen_pareto_optimal_points,
)


def branin_currin(x: TensorType) -> TensorType:
"""
The branincurrin synthetic function.

:param x: The points at which to evaluate the function, with shape [..., d].
:raise ValueError (or InvalidArgumentError): If ``x`` has an invalid shape.
"""
tf.debugging.assert_shapes([(x, (..., 2))])
return tf.concat([branin(x), currin(x)], axis=-1)
TsingQAQ marked this conversation as resolved.
Show resolved Hide resolved


def currin(x: TensorType) -> TensorType:
"""
The currin synthetic function

:param x: The points at which to evaluate the function, with shape [..., d].
:raise ValueError (or InvalidArgumentError): If ``x`` has an invalid shape.
"""
tf.debugging.assert_shapes([(x, (..., 2))])
return (
(1 - tf.math.exp(-0.5 * (1 / (x[..., 1] + 1e-100)))) # 1e-100 used for avoid zero division
* (
(2300 * x[..., 0] ** 3 + 1900 * x[..., 0] ** 2 + 2092 * x[..., 0] + 60)
/ (100 * x[..., 0] ** 3 + 500 * x[..., 0] ** 2 + 4 * x[..., 0] + 20)
)
)[..., tf.newaxis]