Example #1
0
    def test_expected_hypervolume_improvement(self):
        tkwargs = {"device": self.device}
        for dtype in (torch.float, torch.double):
            ref_point = [0.0, 0.0]
            tkwargs["dtype"] = dtype
            pareto_Y = torch.tensor(
                [[4.0, 5.0], [5.0, 5.0], [8.5, 3.5], [8.5, 3.0], [9.0, 1.0]],
                **tkwargs)
            partitioning = NondominatedPartitioning(num_outcomes=2)
            # the event shape is `b x q x m` = 1 x 1 x 1
            mean = torch.zeros(1, 1, 2, **tkwargs)
            variance = torch.zeros(1, 1, 2, **tkwargs)
            mm = MockModel(MockPosterior(mean=mean, variance=variance))
            # test error if there is not pareto_Y initialized in partitioning
            with self.assertRaises(BotorchError):
                ExpectedHypervolumeImprovement(model=mm,
                                               ref_point=ref_point,
                                               partitioning=partitioning)
            partitioning.update(Y=pareto_Y)
            # test error if ref point has wrong shape
            with self.assertRaises(ValueError):
                ExpectedHypervolumeImprovement(model=mm,
                                               ref_point=ref_point[:1],
                                               partitioning=partitioning)

            with self.assertRaises(ValueError):
                # test error if no pareto_Y point is better than ref_point
                ExpectedHypervolumeImprovement(model=mm,
                                               ref_point=[10.0, 10.0],
                                               partitioning=partitioning)
            X = torch.zeros(1, 1, **tkwargs)
            # basic test
            acqf = ExpectedHypervolumeImprovement(model=mm,
                                                  ref_point=ref_point,
                                                  partitioning=partitioning)
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)
            # check ref point
            self.assertTrue(
                torch.equal(acqf.ref_point, torch.tensor(ref_point,
                                                         **tkwargs)))
            # check bounds
            self.assertTrue(hasattr(acqf, "cell_lower_bounds"))
            self.assertTrue(hasattr(acqf, "cell_upper_bounds"))
            # check cached indices
            expected_indices = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]],
                                            dtype=torch.long,
                                            device=self.device)
            self.assertTrue(
                torch.equal(acqf._cross_product_indices, expected_indices))
Example #2
0
    def test_constrained_q_expected_hypervolume_improvement(self):
        for dtype in (torch.float, torch.double):
            tkwargs = {"device": self.device, "dtype": dtype}
            ref_point = [0.0, 0.0]
            pareto_Y = torch.tensor(
                [[4.0, 5.0], [5.0, 5.0], [8.5, 3.5], [8.5, 3.0], [9.0, 1.0]],
                **tkwargs)
            partitioning = NondominatedPartitioning(num_outcomes=2)
            partitioning.update(Y=pareto_Y)

            # test q=1
            # the event shape is `b x q x m` = 1 x 1 x 2
            samples = torch.tensor([[[6.5, 4.5]]], **tkwargs)
            mm = MockModel(MockPosterior(samples=samples))
            sampler = IIDNormalSampler(num_samples=1)
            X = torch.zeros(1, 1, **tkwargs)
            # test zero slack
            for eta in (1e-1, 1e-2):
                acqf = qExpectedHypervolumeImprovement(
                    model=mm,
                    ref_point=ref_point,
                    partitioning=partitioning,
                    sampler=sampler,
                    constraints=[lambda Z: torch.zeros_like(Z[..., -1])],
                    eta=eta,
                )
                res = acqf(X)
                self.assertAlmostEqual(res.item(), 0.5 * 1.5, places=4)
            # test feasible
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
                constraints=[lambda Z: -100.0 * torch.ones_like(Z[..., -1])],
                eta=1e-3,
            )
            res = acqf(X)
            self.assertAlmostEqual(res.item(), 1.5, places=4)
            # test infeasible
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
                constraints=[lambda Z: 100.0 * torch.ones_like(Z[..., -1])],
                eta=1e-3,
            )
            res = acqf(X)
            self.assertAlmostEqual(res.item(), 0.0, places=4)
Example #3
0
    def test_non_dominated_partitioning(self):
        tkwargs = {"device": self.device}
        for dtype in (torch.float, torch.double):
            tkwargs["dtype"] = dtype
            partitioning = NondominatedPartitioning(num_outcomes=2)
            # assert error is raised if pareto_Y has not been computed
            with self.assertRaises(BotorchError):
                partitioning.pareto_Y
            # test eps
            # no pareto_Y
            self.assertEqual(partitioning.eps, 1e-6)
            partitioning = NondominatedPartitioning(num_outcomes=2, eps=1.0)
            # eps set
            self.assertEqual(partitioning.eps, 1.0)
            # set pareto_Y
            partitioning = NondominatedPartitioning(num_outcomes=2)
            Y = torch.zeros(1, 2, **tkwargs)
            partitioning.update(Y=Y)
            self.assertEqual(partitioning.eps, 1e-6 if dtype == torch.float else 1e-8)

            # test _update_pareto_Y
            partitioning.Y = -Y
            self.assertFalse(partitioning._update_pareto_Y())

            # test m=2
            arange = torch.arange(3, 9, **tkwargs)
            pareto_Y = torch.stack([arange, 11 - arange], dim=-1)
            Y = torch.cat(
                [
                    pareto_Y,
                    torch.tensor(
                        [[8.0, 2.0], [7.0, 1.0]], **tkwargs
                    ),  # add some non-pareto elements
                ],
                dim=0,
            )
            partitioning = NondominatedPartitioning(num_outcomes=2, Y=Y)
            sorting = torch.argsort(pareto_Y[:, 0], descending=True)
            self.assertTrue(torch.equal(pareto_Y[sorting], partitioning.pareto_Y))
            ref_point = torch.zeros(2, **tkwargs)
            inf = float("inf")
            expected_cell_bounds = torch.tensor(
                [
                    [
                        [8.0, 0.0],
                        [7.0, 3.0],
                        [6.0, 4.0],
                        [5.0, 5.0],
                        [4.0, 6.0],
                        [3.0, 7.0],
                        [0.0, 8.0],
                    ],
                    [
                        [inf, inf],
                        [8.0, inf],
                        [7.0, inf],
                        [6.0, inf],
                        [5.0, inf],
                        [4.0, inf],
                        [3.0, inf],
                    ],
                ],
                **tkwargs,
            )
            cell_bounds = partitioning.get_hypercell_bounds(ref_point)
            self.assertTrue(torch.equal(cell_bounds, expected_cell_bounds))
            # test compute hypervolume
            hv = partitioning.compute_hypervolume(ref_point)
            self.assertEqual(hv.item(), 49.0)
            # test error when reference is not worse than all pareto_Y
            with self.assertRaises(ValueError):
                partitioning.compute_hypervolume(pareto_Y.max(dim=0).values)

            # test batched, m=2 case
            Y = torch.rand(3, 10, 2, **tkwargs)
            partitioning = NondominatedPartitioning(num_outcomes=2, Y=Y)
            cell_bounds = partitioning.get_hypercell_bounds(ref_point)
            partitionings = []
            for i in range(Y.shape[0]):
                partitioning_i = NondominatedPartitioning(num_outcomes=2, Y=Y[i])
                partitionings.append(partitioning_i)
                # check pareto_Y
                pareto_set1 = {tuple(x) for x in partitioning_i.pareto_Y.tolist()}
                pareto_set2 = {tuple(x) for x in partitioning.pareto_Y[i].tolist()}
                self.assertEqual(pareto_set1, pareto_set2)
                expected_cell_bounds_i = partitioning_i.get_hypercell_bounds(ref_point)
                # remove padding
                no_padding_cell_bounds_i = cell_bounds[:, i][
                    :, ((cell_bounds[1, i] - cell_bounds[0, i]) != 0).all(dim=-1)
                ]
                self.assertTrue(
                    torch.equal(expected_cell_bounds_i, no_padding_cell_bounds_i)
                )

            # test batch ref point
            cell_bounds2 = partitioning.get_hypercell_bounds(
                ref_point.unsqueeze(0).expand(3, 2)
            )
            self.assertTrue(torch.equal(cell_bounds, cell_bounds2))

            # test improper batch shape
            with self.assertRaises(BotorchTensorDimensionError):
                partitioning.get_hypercell_bounds(ref_point.unsqueeze(0).expand(4, 2))

            # test improper Y shape (too many batch dims)
            with self.assertRaises(NotImplementedError):
                NondominatedPartitioning(num_outcomes=2, Y=Y.unsqueeze(0))

            # test batched compute_hypervolume, m=2
            hvs = partitioning.compute_hypervolume(ref_point)
            hvs_non_batch = torch.stack(
                [
                    partitioning_i.compute_hypervolume(ref_point)
                    for partitioning_i in partitionings
                ],
                dim=0,
            )
            self.assertTrue(torch.allclose(hvs, hvs_non_batch))

            # test batched m>2
            with self.assertRaises(NotImplementedError):
                NondominatedPartitioning(
                    num_outcomes=3, Y=torch.cat([Y, Y[..., :1]], dim=-1)
                )

            # test error with partition_non_dominated_space_2d for m=3
            partitioning = NondominatedPartitioning(
                num_outcomes=3, Y=torch.zeros(1, 3, **tkwargs)
            )
            with self.assertRaises(BotorchTensorDimensionError):
                partitioning.partition_non_dominated_space_2d()
            # test m=3
            pareto_Y = torch.tensor(
                [[1.0, 6.0, 8.0], [2.0, 4.0, 10.0], [3.0, 5.0, 7.0]], **tkwargs
            )
            partitioning = NondominatedPartitioning(num_outcomes=3, Y=pareto_Y)
            sorting = torch.argsort(pareto_Y[:, 0], descending=True)
            self.assertTrue(torch.equal(pareto_Y[sorting], partitioning.pareto_Y))
            ref_point = torch.tensor([-1.0, -2.0, -3.0], **tkwargs)
            expected_cell_bounds = torch.tensor(
                [
                    [
                        [1.0, 4.0, 7.0],
                        [-1.0, -2.0, 10.0],
                        [-1.0, 4.0, 8.0],
                        [1.0, -2.0, 10.0],
                        [1.0, 4.0, 8.0],
                        [-1.0, 6.0, -3.0],
                        [1.0, 5.0, -3.0],
                        [-1.0, 5.0, 8.0],
                        [2.0, -2.0, 7.0],
                        [2.0, 4.0, 7.0],
                        [3.0, -2.0, -3.0],
                        [2.0, -2.0, 8.0],
                        [2.0, 5.0, -3.0],
                    ],
                    [
                        [2.0, 5.0, 8.0],
                        [1.0, 4.0, inf],
                        [1.0, 5.0, inf],
                        [2.0, 4.0, inf],
                        [2.0, 5.0, inf],
                        [1.0, inf, 8.0],
                        [2.0, inf, 8.0],
                        [2.0, inf, inf],
                        [3.0, 4.0, 8.0],
                        [3.0, 5.0, 8.0],
                        [inf, 5.0, 8.0],
                        [inf, 5.0, inf],
                        [inf, inf, inf],
                    ],
                ],
                **tkwargs,
            )
            cell_bounds = partitioning.get_hypercell_bounds(ref_point)
            # cell bounds can have different order
            num_matches = (
                (cell_bounds.unsqueeze(0) == expected_cell_bounds.unsqueeze(1))
                .all(dim=-1)
                .any(dim=0)
                .sum()
            )
            self.assertTrue(num_matches, 9)
            # test compute hypervolume
            hv = partitioning.compute_hypervolume(ref_point)
            self.assertEqual(hv.item(), 358.0)
Example #4
0
    def test_q_expected_hypervolume_improvement(self):
        tkwargs = {"device": self.device}
        for dtype in (torch.float, torch.double):
            ref_point = [0.0, 0.0]
            tkwargs["dtype"] = dtype
            pareto_Y = torch.tensor(
                [[4.0, 5.0], [5.0, 5.0], [8.5, 3.5], [8.5, 3.0], [9.0, 1.0]],
                **tkwargs)
            partitioning = NondominatedPartitioning(num_outcomes=2)
            # the event shape is `b x q x m` = 1 x 1 x 2
            samples = torch.zeros(1, 1, 2, **tkwargs)
            mm = MockModel(MockPosterior(samples=samples))
            # test error if there is not pareto_Y initialized in partitioning
            with self.assertRaises(BotorchError):
                qExpectedHypervolumeImprovement(model=mm,
                                                ref_point=ref_point,
                                                partitioning=partitioning)
            partitioning.update(Y=pareto_Y)
            # test error if ref point has wrong shape
            with self.assertRaises(ValueError):
                qExpectedHypervolumeImprovement(model=mm,
                                                ref_point=ref_point[:1],
                                                partitioning=partitioning)

            X = torch.zeros(1, 1, **tkwargs)
            # basic test
            sampler = IIDNormalSampler(num_samples=1)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)
            # check ref point
            self.assertTrue(
                torch.equal(acqf.ref_point, torch.tensor(ref_point,
                                                         **tkwargs)))
            # check cached indices
            self.assertTrue(hasattr(acqf, "q_subset_indices"))
            self.assertIn("q_choose_1", acqf.q_subset_indices)
            self.assertTrue(
                torch.equal(
                    acqf.q_subset_indices["q_choose_1"],
                    torch.tensor([[0]], device=self.device),
                ))

            # test q=2
            X2 = torch.zeros(2, 1, **tkwargs)
            samples2 = torch.zeros(1, 2, 2, **tkwargs)
            mm2 = MockModel(MockPosterior(samples=samples2))
            acqf.model = mm2
            res = acqf(X2)
            self.assertEqual(res.item(), 0.0)
            # check cached indices
            self.assertTrue(hasattr(acqf, "q_subset_indices"))
            self.assertIn("q_choose_1", acqf.q_subset_indices)
            self.assertTrue(
                torch.equal(
                    acqf.q_subset_indices["q_choose_1"],
                    torch.tensor([[0], [1]], device=self.device),
                ))
            self.assertIn("q_choose_2", acqf.q_subset_indices)
            self.assertTrue(
                torch.equal(
                    acqf.q_subset_indices["q_choose_2"],
                    torch.tensor([[0, 1]], device=self.device),
                ))
            self.assertNotIn("q_choose_3", acqf.q_subset_indices)
            # now back to 1 and sure all caches were cleared
            acqf.model = mm
            res = acqf(X)
            self.assertNotIn("q_choose_2", acqf.q_subset_indices)
            self.assertIn("q_choose_1", acqf.q_subset_indices)
            self.assertTrue(
                torch.equal(
                    acqf.q_subset_indices["q_choose_1"],
                    torch.tensor([[0]], device=self.device),
                ))

            X = torch.zeros(1, 1, **tkwargs)
            samples = torch.zeros(1, 1, 2, **tkwargs)
            mm = MockModel(MockPosterior(samples=samples))
            # basic test, no resample
            sampler = IIDNormalSampler(num_samples=2, seed=12345)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)
            self.assertEqual(acqf.sampler.base_samples.shape,
                             torch.Size([2, 1, 1, 2]))
            bs = acqf.sampler.base_samples.clone()
            res = acqf(X)
            self.assertTrue(torch.equal(acqf.sampler.base_samples, bs))

            # basic test, qmc, no resample
            sampler = SobolQMCNormalSampler(num_samples=2)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)
            self.assertEqual(acqf.sampler.base_samples.shape,
                             torch.Size([2, 1, 1, 2]))
            bs = acqf.sampler.base_samples.clone()
            acqf(X)
            self.assertTrue(torch.equal(acqf.sampler.base_samples, bs))

            # basic test, qmc, resample
            sampler = SobolQMCNormalSampler(num_samples=2, resample=True)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)
            self.assertEqual(acqf.sampler.base_samples.shape,
                             torch.Size([2, 1, 1, 2]))
            bs = acqf.sampler.base_samples.clone()
            acqf(X)
            self.assertFalse(torch.equal(acqf.sampler.base_samples, bs))

            # basic test for X_pending and warning
            acqf.set_X_pending()
            self.assertIsNone(acqf.X_pending)
            acqf.set_X_pending(None)
            self.assertIsNone(acqf.X_pending)
            acqf.set_X_pending(X)
            self.assertEqual(acqf.X_pending, X)
            res = acqf(X)
            X2 = torch.zeros(1, 1, 1, requires_grad=True, **tkwargs)
            with warnings.catch_warnings(
                    record=True) as ws, settings.debug(True):
                acqf.set_X_pending(X2)
                self.assertEqual(acqf.X_pending, X2)
                self.assertEqual(len(ws), 1)
                self.assertTrue(issubclass(ws[-1].category, BotorchWarning))

            # test objective
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
                objective=IdentityMCMultiOutputObjective(),
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)

            # Test that the hypervolume improvement is correct for given sample
            # test q = 1
            X = torch.zeros(1, 1, **tkwargs)
            # basic test
            samples = torch.tensor([[[6.5, 4.5]]], **tkwargs)
            mm = MockModel(MockPosterior(samples=samples))
            sampler = IIDNormalSampler(num_samples=1)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 1.5)
            # test q = 1, does not contribute
            samples = torch.tensor([0.0, 1.0], **tkwargs).view(1, 1, 2)
            sampler = IIDNormalSampler(1)
            mm = MockModel(MockPosterior(samples=samples))
            acqf.model = mm
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)

            # test q = 2, both points contribute
            X = torch.zeros(2, 1, **tkwargs)
            samples = torch.tensor([[6.5, 4.5], [7.0, 4.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf.model = mm
            res = acqf(X)
            self.assertEqual(res.item(), 1.75)

            # test q = 2, only 1 point contributes
            samples = torch.tensor([[6.5, 4.5], [6.0, 4.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf.model = mm
            res = acqf(X)
            self.assertEqual(res.item(), 1.5)

            # test q = 2, neither contributes
            samples = torch.tensor([[2.0, 2.0], [0.0, 0.1]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf.model = mm
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)

            # test q = 2, test point better than current best second objective
            samples = torch.tensor([[6.5, 4.5], [6.0, 6.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf.model = mm
            res = acqf(X)
            self.assertEqual(res.item(), 8.0)

            # test q = 2, test point better than current-best first objective
            samples = torch.tensor([[6.5, 4.5], [9.0, 2.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 2.0)
            # test q = 3, all contribute
            X = torch.zeros(3, 1, **tkwargs)
            samples = torch.tensor([[6.5, 4.5], [9.0, 2.0], [7.0, 4.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 2.25)
            # test q = 3, not all contribute
            samples = torch.tensor([[6.5, 4.5], [9.0, 2.0], [7.0, 5.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 3.5)
            # test q = 3, none contribute
            samples = torch.tensor([[0.0, 4.5], [1.0, 2.0], [3.0, 0.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)

            # test m = 3, q=1
            pareto_Y = torch.tensor(
                [[4.0, 2.0, 3.0], [3.0, 5.0, 1.0], [2.0, 4.0, 2.0],
                 [1.0, 3.0, 4.0]],
                **tkwargs,
            )
            partitioning = NondominatedPartitioning(num_outcomes=3, Y=pareto_Y)
            samples = torch.tensor([[1.0, 2.0, 6.0]], **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            ref_point = [-1.0] * 3
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            X = torch.zeros(1, 2, **tkwargs)
            res = acqf(X)
            self.assertEqual(res.item(), 12.0)

            # change reference point
            ref_point = [0.0] * 3
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 4.0)

            # test m = 3, no contribution
            ref_point = [1.0] * 3
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            res = acqf(X)
            self.assertEqual(res.item(), 0.0)

            # test m = 3, q = 2
            pareto_Y = torch.tensor(
                [[4.0, 2.0, 3.0], [3.0, 5.0, 1.0], [2.0, 4.0, 2.0]], **tkwargs)
            samples = torch.tensor([[1.0, 2.0, 6.0], [1.0, 3.0, 4.0]],
                                   **tkwargs).unsqueeze(0)
            mm = MockModel(MockPosterior(samples=samples))
            ref_point = [-1.0] * 3
            partitioning = NondominatedPartitioning(num_outcomes=3, Y=pareto_Y)
            acqf = qExpectedHypervolumeImprovement(
                model=mm,
                ref_point=ref_point,
                partitioning=partitioning,
                sampler=sampler,
            )
            X = torch.zeros(2, 2, **tkwargs)
            res = acqf(X)
            self.assertEqual(res.item(), 22.0)
    def test_non_dominated_partitioning(self):
        tkwargs = {"device": self.device}
        for dtype in (torch.float, torch.double):
            tkwargs["dtype"] = dtype
            partitioning = NondominatedPartitioning(num_outcomes=2)
            # assert error is raised if pareto_Y has not been computed
            with self.assertRaises(BotorchError):
                partitioning.pareto_Y
            # test eps
            # no pareto_Y
            self.assertEqual(partitioning.eps, 1e-6)
            partitioning = NondominatedPartitioning(num_outcomes=2, eps=1.0)
            # eps set
            self.assertEqual(partitioning.eps, 1.0)
            # set pareto_Y
            partitioning = NondominatedPartitioning(num_outcomes=2)
            Y = torch.zeros(1, 2, **tkwargs)
            partitioning.update(Y=Y)
            self.assertEqual(partitioning.eps,
                             1e-6 if dtype == torch.float else 1e-8)

            # test _update_pareto_Y
            partitioning.Y = -Y
            self.assertFalse(partitioning._update_pareto_Y())

            # test m=2
            arange = torch.arange(3, 9, **tkwargs)
            pareto_Y = torch.stack([arange, 11 - arange], dim=-1)
            Y = torch.cat(
                [
                    pareto_Y,
                    torch.tensor([[8.0, 2.0], [7.0, 1.0]], **
                                 tkwargs),  # add some non-pareto elements
                ],
                dim=0,
            )
            partitioning = NondominatedPartitioning(num_outcomes=2, Y=Y)
            sorting = torch.argsort(pareto_Y[:, 0], descending=True)
            self.assertTrue(
                torch.equal(pareto_Y[sorting], partitioning.pareto_Y))
            ref_point = torch.zeros(2, **tkwargs)
            inf = float("inf")
            expected_cell_bounds = torch.tensor([
                [
                    [8.0, 0.0],
                    [7.0, 3.0],
                    [6.0, 4.0],
                    [5.0, 5.0],
                    [4.0, 6.0],
                    [3.0, 7.0],
                    [0.0, 8.0],
                ],
                [
                    [inf, inf],
                    [8.0, inf],
                    [7.0, inf],
                    [6.0, inf],
                    [5.0, inf],
                    [4.0, inf],
                    [3.0, inf],
                ],
            ], **tkwargs)
            cell_bounds = partitioning.get_hypercell_bounds(ref_point)
            self.assertTrue(torch.equal(cell_bounds, expected_cell_bounds))
            # test compute hypervolume
            hv = partitioning.compute_hypervolume(ref_point)
            self.assertEqual(hv, 49.0)
            # test error when reference is not worse than all pareto_Y
            with self.assertRaises(ValueError):
                partitioning.compute_hypervolume(pareto_Y.max(dim=0).values)

            # test error with partition_non_dominated_space_2d for m=3
            partitioning = NondominatedPartitioning(num_outcomes=3,
                                                    Y=torch.zeros(
                                                        1, 3, **tkwargs))
            with self.assertRaises(BotorchTensorDimensionError):
                partitioning.partition_non_dominated_space_2d()
            # test m=3
            pareto_Y = torch.tensor(
                [[1.0, 6.0, 8.0], [2.0, 4.0, 10.0], [3.0, 5.0, 7.0]],
                **tkwargs)
            partitioning = NondominatedPartitioning(num_outcomes=3, Y=pareto_Y)
            sorting = torch.argsort(pareto_Y[:, 0], descending=True)
            self.assertTrue(
                torch.equal(pareto_Y[sorting], partitioning.pareto_Y))
            ref_point = torch.tensor([-1.0, -2.0, -3.0], **tkwargs)
            expected_cell_bounds = torch.tensor([
                [
                    [1.0, 4.0, 7.0],
                    [-1.0, -2.0, 10.0],
                    [-1.0, 4.0, 8.0],
                    [1.0, -2.0, 10.0],
                    [1.0, 4.0, 8.0],
                    [-1.0, 6.0, -3.0],
                    [1.0, 5.0, -3.0],
                    [-1.0, 5.0, 8.0],
                    [2.0, -2.0, 7.0],
                    [2.0, 4.0, 7.0],
                    [3.0, -2.0, -3.0],
                    [2.0, -2.0, 8.0],
                    [2.0, 5.0, -3.0],
                ],
                [
                    [2.0, 5.0, 8.0],
                    [1.0, 4.0, inf],
                    [1.0, 5.0, inf],
                    [2.0, 4.0, inf],
                    [2.0, 5.0, inf],
                    [1.0, inf, 8.0],
                    [2.0, inf, 8.0],
                    [2.0, inf, inf],
                    [3.0, 4.0, 8.0],
                    [3.0, 5.0, 8.0],
                    [inf, 5.0, 8.0],
                    [inf, 5.0, inf],
                    [inf, inf, inf],
                ],
            ], **tkwargs)
            cell_bounds = partitioning.get_hypercell_bounds(ref_point)
            # cell bounds can have different order
            num_matches = ((cell_bounds.unsqueeze(0) == expected_cell_bounds.
                            unsqueeze(1)).all(dim=-1).any(dim=0).sum())
            self.assertTrue(num_matches, 9)
            # test compute hypervolume
            hv = partitioning.compute_hypervolume(ref_point)
            self.assertEqual(hv, 358.0)