diff --git a/blackboxopt/visualizations/utils.py b/blackboxopt/visualizations/utils.py index 6469b898..a69de108 100644 --- a/blackboxopt/visualizations/utils.py +++ b/blackboxopt/visualizations/utils.py @@ -173,13 +173,23 @@ def add_plotly_buttons_for_logscale(fig): def mask_pareto_efficient(costs: np.ndarray): + """For a given array of objective values where lower values are considered better + and the dimensions are samples x objectives, return a mask that is `True` for all + pareto efficient values. + + NOTE: The result marks multiple occurrences of the same point all as pareto + efficient. + """ is_efficient = np.ones(costs.shape[0], dtype=bool) for i, c in enumerate(costs): - if is_efficient[i]: - is_efficient[is_efficient] = np.any( - costs[is_efficient] <= c, axis=1 - ) # Keep any point with a lower cost - is_efficient[i] = True # And keep self + if not is_efficient[i]: + continue + + # Keep any point with a lower cost or when they are the same + efficient = np.any(costs[is_efficient] < c, axis=1) + duplicates = np.all(costs[is_efficient] == c, axis=1) + is_efficient[is_efficient] = np.logical_or(efficient, duplicates) + return is_efficient diff --git a/pyproject.toml b/pyproject.toml index adfbeca5..568a07b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "blackboxopt" -version = "4.3.2" +version = "4.3.3" description = "A common interface for blackbox optimization algorithms along with useful helpers like parallel optimization loops, analysis and visualization scripts." readme = "README.md" repository = "https://github.com/boschresearch/blackboxopt" diff --git a/tests/visualization_test.py b/tests/visualization_test.py index a28f170b..677375a8 100644 --- a/tests/visualization_test.py +++ b/tests/visualization_test.py @@ -216,7 +216,6 @@ def test_multi_objective_visualization_without_fidelities(): def test_mask_pareto_efficient(): - evals = np.array( [ [0.0, 1.0], @@ -224,15 +223,21 @@ def test_mask_pareto_efficient(): [0.0, 1.0], [1.0, 0.0], [3.1, 1.1], + [0.1, 1.0], + [0.0, 1.1], + [-1.0, 2.0], ] ) pareto_efficient = mask_pareto_efficient(evals) - assert len(pareto_efficient) == 5 + assert len(pareto_efficient) == evals.shape[0] assert pareto_efficient[0] assert not pareto_efficient[1] assert pareto_efficient[2] assert pareto_efficient[3] assert not pareto_efficient[4] + assert not pareto_efficient[5] + assert not pareto_efficient[6] + assert pareto_efficient[7] def test_prepare_for_multi_objective_visualization_handles_score_objectives():