Skip to content

Commit f76979d

Browse files
esantorellafacebook-github-bot
authored andcommitted
Fix box decomposition behavior with empty or None Y (#1489)
Summary: Pull Request resolved: #1489 See T126108893 Reviewed By: SebastianAment Differential Revision: D41170229 fbshipit-source-id: 0325791c1f190bb3a3f09c427097c5c0582c18dc
1 parent 1e8e83c commit f76979d

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

botorch/utils/multi_objective/box_decompositions/dominated.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def compute_hypervolume(self) -> Tensor:
5656
A `(batch_shape)`-dim tensor containing the hypervolume dominated by
5757
each Pareto frontier.
5858
"""
59+
if not hasattr(self, "_neg_pareto_Y"):
60+
return torch.tensor(0.0).to(self._neg_ref_point)
61+
5962
if self._neg_pareto_Y.shape[-2] == 0:
6063
return torch.zeros(
6164
self._neg_pareto_Y.shape[:-2],

botorch/utils/multi_objective/box_decompositions/non_dominated.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ def compute_hypervolume(self) -> Tensor:
347347
Returns:
348348
`(batch_shape)`-dim tensor containing the dominated hypervolume.
349349
"""
350+
if not hasattr(self, "_neg_pareto_Y"):
351+
return torch.tensor(0.0).to(self._neg_ref_point)
352+
350353
if self._neg_pareto_Y.shape[-2] == 0:
351354
return torch.zeros(
352355
self._neg_pareto_Y.shape[:-2],
@@ -460,13 +463,15 @@ def _partition_space_2d(self) -> None:
460463
)
461464
self.register_buffer("hypercell_bounds", cell_bounds)
462465

463-
def compute_hypervolume(self):
466+
def compute_hypervolume(self) -> Tensor:
464467
r"""Compute hypervolume that is dominated by the Pareto Froniter.
465468
466469
Returns:
467470
A `(batch_shape)`-dim tensor containing the hypervolume dominated by
468471
each Pareto frontier.
469472
"""
473+
if not hasattr(self, "_neg_pareto_Y"):
474+
return torch.tensor(0.0).to(self._neg_ref_point)
470475
if self._neg_pareto_Y.shape[-2] == 0:
471476
return torch.zeros(
472477
self._neg_pareto_Y.shape[:-2],

test/utils/multi_objective/box_decompositions/test_box_decomposition.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
BoxDecomposition,
1616
FastPartitioning,
1717
)
18+
from botorch.utils.multi_objective.box_decompositions.dominated import (
19+
DominatedPartitioning,
20+
)
21+
from botorch.utils.multi_objective.box_decompositions.non_dominated import (
22+
FastNondominatedPartitioning,
23+
NondominatedPartitioning,
24+
)
1825
from botorch.utils.multi_objective.box_decompositions.utils import (
1926
update_local_upper_bounds_incremental,
2027
)
@@ -262,3 +269,50 @@ def test_fast_partitioning(self):
262269
if m == 2:
263270
with self.assertRaises(NotImplementedError):
264271
DummyFastPartitioning(ref_point=ref_point, Y=Y.unsqueeze(0))
272+
273+
274+
class TestBoxDecomposition_Hypervolume(BotorchTestCase):
275+
def helper_hypervolume(self, Box_Decomp_cls: type) -> None:
276+
"""
277+
This test should be run for each non-abstract subclass of `BoxDecomposition`.
278+
"""
279+
# batching
280+
n_outcomes, batch_dim, n = 2, 3, 4
281+
282+
ref_point = torch.zeros(n_outcomes)
283+
Y = torch.ones(batch_dim, n, n_outcomes)
284+
285+
box_decomp = Box_Decomp_cls(ref_point=ref_point, Y=Y)
286+
hv = box_decomp.compute_hypervolume()
287+
self.assertEqual(hv.shape, (batch_dim,))
288+
self.assertTrue(torch.allclose(hv, torch.ones(batch_dim)))
289+
290+
# no batching
291+
Y = torch.ones(n, n_outcomes)
292+
293+
box_decomp = Box_Decomp_cls(ref_point=ref_point, Y=Y)
294+
hv = box_decomp.compute_hypervolume()
295+
296+
self.assertEqual(hv.shape, ())
297+
self.assertTrue(torch.allclose(hv, torch.tensor(1.0)))
298+
299+
# cases where there is nothing in Y, either because n=0 or Y is None
300+
n = 0
301+
Y_and_expected_shape = [
302+
(torch.ones(batch_dim, n, n_outcomes), (batch_dim,)),
303+
(torch.ones(n, n_outcomes), ()),
304+
(None, ()),
305+
]
306+
for Y, expected_shape in Y_and_expected_shape:
307+
box_decomp = Box_Decomp_cls(ref_point=ref_point, Y=Y)
308+
hv = box_decomp.compute_hypervolume()
309+
self.assertEqual(hv.shape, expected_shape)
310+
self.assertTrue(torch.allclose(hv, torch.tensor(0.0)))
311+
312+
def test_hypervolume(self) -> None:
313+
for cl in [
314+
NondominatedPartitioning,
315+
DominatedPartitioning,
316+
FastNondominatedPartitioning,
317+
]:
318+
self.helper_hypervolume(cl)

0 commit comments

Comments
 (0)