def test_initialize_q_batch_nonneg(self, cuda=False): device = torch.device("cuda") if cuda else torch.device("cpu") for dtype in (torch.float, torch.double): # basic test X = torch.rand(5, 3, 4, device=device, dtype=dtype) Y = torch.rand(5, device=device, dtype=dtype) ics = initialize_q_batch_nonneg(X=X, Y=Y, n=2) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) self.assertEqual(ics.device, X.device) self.assertEqual(ics.dtype, X.dtype) # ensure nothing happens if we want all samples ics = initialize_q_batch_nonneg(X=X, Y=Y, n=5) self.assertTrue(torch.equal(X, ics)) # make sure things work with constant inputs Y = torch.ones(5, device=device, dtype=dtype) ics = initialize_q_batch_nonneg(X=X, Y=Y, n=2) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) self.assertEqual(ics.device, X.device) self.assertEqual(ics.dtype, X.dtype) # ensure raises correct warning Y = torch.zeros(5, device=device, dtype=dtype) with warnings.catch_warnings( record=True) as w, settings.debug(True): ics = initialize_q_batch_nonneg(X=X, Y=Y, n=2) self.assertEqual(len(w), 1) self.assertTrue( issubclass(w[-1].category, BadInitialCandidatesWarning)) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) with self.assertRaises(RuntimeError): initialize_q_batch_nonneg(X=X, Y=Y, n=10) # test less than `n` positive acquisition values Y = torch.arange(5, device=device, dtype=dtype) - 3 ics = initialize_q_batch_nonneg(X=X, Y=Y, n=2) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) self.assertEqual(ics.device, X.device) self.assertEqual(ics.dtype, X.dtype) # check that we chose the point with the positive acquisition value self.assertTrue( torch.equal(ics[0], X[-1]) or torch.equal(ics[1], X[-1])) # test less than `n` alpha_pos values Y = torch.arange(5, device=device, dtype=dtype) ics = initialize_q_batch_nonneg(X=X, Y=Y, n=2, alpha=1.0) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) self.assertEqual(ics.device, X.device) self.assertEqual(ics.dtype, X.dtype)
def test_gen_batch_initial_conditions_highdim(self): d = 2200 # 2200 * 10 (q) > 21201 (sobol max dim) bounds = torch.stack([torch.zeros(d), torch.ones(d)]) ffs_map = {i: random() for i in range(0, d, 2)} mock_acqf = MockAcquisitionFunction() mock_acqf.objective = lambda y: y.squeeze(-1) for dtype in (torch.float, torch.double): bounds = bounds.to(device=self.device, dtype=dtype) mock_acqf.X_baseline = bounds # for testing sample_around_best mock_acqf.model = MockModel(MockPosterior(mean=bounds[:, :1])) for nonnegative, seed, ffs, sample_around_best in product( [True, False], [None, 1234], [None, ffs_map], [True, False]): with warnings.catch_warnings( record=True) as ws, settings.debug(True): batch_initial_conditions = gen_batch_initial_conditions( acq_function=MockAcquisitionFunction(), bounds=bounds, q=10, num_restarts=1, raw_samples=2, fixed_features=ffs, options={ "nonnegative": nonnegative, "eta": 0.01, "alpha": 0.1, "seed": seed, "sample_around_best": sample_around_best, }, ) self.assertTrue( any( issubclass(w.category, SamplingWarning) for w in ws)) expected_shape = torch.Size([1, 10, d]) self.assertEqual(batch_initial_conditions.shape, expected_shape) self.assertEqual(batch_initial_conditions.device, bounds.device) self.assertEqual(batch_initial_conditions.dtype, bounds.dtype) if ffs is not None: for idx, val in ffs.items(): self.assertTrue( torch.all(batch_initial_conditions[..., idx] == val))
def test_fit_gpytorch_model_singular(self): options = {"disp": False, "maxiter": 5} for dtype in (torch.float, torch.double): X_train = torch.rand(2, 2, device=self.device, dtype=dtype) Y_train = torch.zeros(2, 1, device=self.device, dtype=dtype) test_likelihood = GaussianLikelihood(noise_constraint=GreaterThan( -1.0, transform=None, initial_value=0.0)) gp = SingleTaskGP(X_train, Y_train, likelihood=test_likelihood) mll = ExactMarginalLogLikelihood(gp.likelihood, gp) mll.to(device=self.device, dtype=dtype) # this will do multiple retries (and emit warnings, which is desired) with warnings.catch_warnings( record=True) as ws, settings.debug(True): fit_gpytorch_model(mll, options=options, max_retries=2) self.assertTrue( any( issubclass(w.category, OptimizationWarning) for w in ws))
def test_InverseCostWeightedUtility(self): for batch_shape in ([], [2]): for dtype in (torch.float, torch.double): # the event shape is `batch_shape x q x t` mean = 1 + torch.rand( *batch_shape, 2, 1, device=self.device, dtype=dtype ) mm = MockModel(MockPosterior(mean=mean)) X = torch.randn(*batch_shape, 3, 2, device=self.device, dtype=dtype) deltas = torch.rand(4, *batch_shape, device=self.device, dtype=dtype) # test that sampler is required if use_mean=False icwu = InverseCostWeightedUtility(mm, use_mean=False) with self.assertRaises(RuntimeError): icwu(X, deltas) # check warning for negative cost mm = MockModel(MockPosterior(mean=mean.clamp_max(-1e-6))) icwu = InverseCostWeightedUtility(mm) with warnings.catch_warnings(record=True) as ws, settings.debug(True): icwu(X, deltas) self.assertTrue( any(issubclass(w.category, CostAwareWarning) for w in ws) ) # basic test mm = MockModel(MockPosterior(mean=mean)) icwu = InverseCostWeightedUtility(mm) ratios = icwu(X, deltas) self.assertTrue( torch.equal(ratios, deltas / mean.squeeze(-1).sum(dim=-1)) ) # sampling test samples = 1 + torch.rand( # event shape is q x m *batch_shape, 3, 1, device=self.device, dtype=dtype ) mm = MockModel(MockPosterior(samples=samples)) icwu = InverseCostWeightedUtility(mm, use_mean=False) ratios = icwu(X, deltas, sampler=IIDNormalSampler(4)) self.assertTrue( torch.equal(ratios, deltas / samples.squeeze(-1).sum(dim=-1)) )
def test_generic_mc_objective_deprecated(self): for dtype in (torch.float, torch.double): with warnings.catch_warnings(record=True) as ws, settings.debug(True): obj = GenericMCObjective(generic_obj_deprecated) warning_msg = ( "The `objective` callable of `GenericMCObjective` is expected to " "take two arguments. Passing a callable that expects a single " "argument will result in an error in future versions." ) self.assertTrue( any(issubclass(w.category, DeprecationWarning) for w in ws) ) self.assertTrue(any(warning_msg in str(w.message) for w in ws)) samples = torch.randn(1, device=self.device, dtype=dtype) self.assertTrue(torch.equal(obj(samples), generic_obj(samples))) samples = torch.randn(2, device=self.device, dtype=dtype) self.assertTrue(torch.equal(obj(samples), generic_obj(samples))) samples = torch.randn(3, 1, device=self.device, dtype=dtype) self.assertTrue(torch.equal(obj(samples), generic_obj(samples))) samples = torch.randn(3, 2, device=self.device, dtype=dtype) self.assertTrue(torch.equal(obj(samples), generic_obj(samples)))
def test_fit_gpytorch_model_sequential(self): options = {"disp": False, "maxiter": 1} for double, kind, outcome_transform in product( (False, True), ("SingleTaskGP", "FixedNoiseGP", "HeteroskedasticSingleTaskGP"), (False, True), ): with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll = self._getBatchedModel( kind=kind, double=double, outcome_transform=outcome_transform) mll = fit_gpytorch_model(mll, options=options, max_retries=1) mll = self._getBatchedModel( kind=kind, double=double, outcome_transform=outcome_transform) mll = fit_gpytorch_model(mll, options=options, sequential=True, max_retries=1) mll = self._getBatchedModel( kind=kind, double=double, outcome_transform=outcome_transform) mll = fit_gpytorch_model(mll, options=options, sequential=False, max_retries=1) if kind == "HeteroskedasticSingleTaskGP": self.assertTrue( any( issubclass(w.category, BotorchWarning) for w in ws)) self.assertTrue( any("Failed to convert ModelList to batched model" in str(w.message) for w in ws))
def test_initialize_q_batch(self): for dtype in (torch.float, torch.double): # basic test X = torch.rand(5, 3, 4, device=self.device, dtype=dtype) Y = torch.rand(5, device=self.device, dtype=dtype) ics = initialize_q_batch(X=X, Y=Y, n=2) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) self.assertEqual(ics.device, X.device) self.assertEqual(ics.dtype, X.dtype) # ensure nothing happens if we want all samples ics = initialize_q_batch(X=X, Y=Y, n=5) self.assertTrue(torch.equal(X, ics)) # ensure raises correct warning Y = torch.zeros(5, device=self.device, dtype=dtype) with warnings.catch_warnings( record=True) as w, settings.debug(True): ics = initialize_q_batch(X=X, Y=Y, n=2) self.assertEqual(len(w), 1) self.assertTrue( issubclass(w[-1].category, BadInitialCandidatesWarning)) self.assertEqual(ics.shape, torch.Size([2, 3, 4])) with self.assertRaises(RuntimeError): initialize_q_batch(X=X, Y=Y, n=10)
def test_construct_base_samples(self): test_shapes = [ {"batch": [2], "output": [4, 3], "sample": [5]}, {"batch": [1], "output": [5, 3], "sample": [5, 6]}, {"batch": [2, 3], "output": [2, 3], "sample": [5]}, ] for tshape, qmc, seed, dtype in itertools.product( test_shapes, (False, True), (None, 1234), (torch.float, torch.double) ): batch_shape = torch.Size(tshape["batch"]) output_shape = torch.Size(tshape["output"]) sample_shape = torch.Size(tshape["sample"]) expected_shape = sample_shape + batch_shape + output_shape samples = construct_base_samples( batch_shape=batch_shape, output_shape=output_shape, sample_shape=sample_shape, qmc=qmc, seed=seed, device=self.device, dtype=dtype, ) self.assertEqual(samples.shape, expected_shape) self.assertEqual(samples.device.type, self.device.type) self.assertEqual(samples.dtype, dtype) # check that warning is issued if dimensionality is too large with warnings.catch_warnings(record=True) as w, settings.debug(True): construct_base_samples( batch_shape=torch.Size(), output_shape=torch.Size([200, 6]), sample_shape=torch.Size([1]), qmc=True, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, SamplingWarning)) exp_str = f"maximum supported by qmc ({SobolEngine.MAXDIM})" self.assertTrue(exp_str in str(w[-1].message))
def test_validate_input_scaling(self): train_X = 2 + torch.rand(3, 4, 3) train_Y = torch.randn(3, 4, 2) # check that nothing is being checked with settings.validate_input_scaling(False), settings.debug(True): with warnings.catch_warnings(record=True) as ws: validate_input_scaling(train_X=train_X, train_Y=train_Y) self.assertFalse( any(issubclass(w.category, InputDataWarning) for w in ws)) # check that warnings are being issued with settings.debug(True), warnings.catch_warnings(record=True) as ws: validate_input_scaling(train_X=train_X, train_Y=train_Y) self.assertTrue( any(issubclass(w.category, InputDataWarning) for w in ws)) # check that errors are raised when requested with settings.debug(True): with self.assertRaises(InputDataError): validate_input_scaling(train_X=train_X, train_Y=train_Y, raise_on_fail=True) # check that no errors are being raised if everything is standardized train_X_min = train_X.min(dim=-1, keepdim=True)[0] train_X_max = train_X.max(dim=-1, keepdim=True)[0] train_X_std = (train_X - train_X_min) / (train_X_max - train_X_min) train_Y_std = (train_Y - train_Y.mean( dim=-2, keepdim=True)) / train_Y.std(dim=-2, keepdim=True) with settings.debug(True), warnings.catch_warnings(record=True) as ws: validate_input_scaling(train_X=train_X_std, train_Y=train_Y_std) self.assertFalse( any(issubclass(w.category, InputDataWarning) for w in ws)) # test that negative variances raise an error train_Yvar = torch.rand_like(train_Y_std) train_Yvar[0, 0, 1] = -0.5 with settings.debug(True): with self.assertRaises(InputDataError): validate_input_scaling(train_X=train_X_std, train_Y=train_Y_std, train_Yvar=train_Yvar) # check that NaNs raise errors train_X_std[0, 0, 0] = float("nan") with settings.debug(True): with self.assertRaises(InputDataError): validate_input_scaling(train_X=train_X_std, train_Y=train_Y_std)
def test_q_expected_improvement(self): for dtype in (torch.float, torch.double): tkwargs = {"device": self.device, "dtype": dtype} # the event shape is `b x q x t` = 1 x 1 x 1 samples = torch.zeros(1, 1, 1, **tkwargs) mm = MockModel(MockPosterior(samples=samples)) # X is `q x d` = 1 x 1. X is a dummy and unused b/c of mocking X = torch.zeros(1, 1, **tkwargs) # basic test sampler = IIDNormalSampler(num_samples=2) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) # test shifting best_f value acqf = qExpectedImprovement(model=mm, best_f=-1, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 1.0) # TODO: Test batched best_f, batched model, batched evaluation # basic test, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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) mm._posterior._samples = torch.zeros(1, 2, 1, **tkwargs) res = acqf(X) X2 = torch.zeros(1, 1, 1, **tkwargs, requires_grad=True) 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))
def test_sample_points_around_best(self): tkwargs = {"device": self.device} _bounds = torch.ones(2, 2) _bounds[1] = 2 for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype bounds = _bounds.to(**tkwargs) X_train = 1 + torch.rand(20, 2, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True)) ) # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) with mock.patch( "botorch.optim.initializers.sample_perturbed_subset_dims" ) as mock_subset_dims: X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) mock_subset_dims.assert_not_called() self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test model that returns a batched mean model = MockModel( MockPosterior( mean=(2 * X_train + 1).sum(dim=-1, keepdim=True).unsqueeze(0) ) ) acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test EI without X_baseline acqf = qExpectedImprovement(model, best_f=0.0) with warnings.catch_warnings(record=True) as w, settings.debug(True): X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # set train inputs model.train_inputs = (X_train,) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test an acquisition function that has objective=None # and maximize=False pm = PosteriorMean(model, maximize=False) self.assertIsNone(pm.objective) self.assertFalse(pm.maximize) X_rnd = sample_points_around_best( acq_function=pm, n_discrete_points=4, sigma=0, bounds=bounds, best_pct=1e-8, # ensures that we only use best value ) idx = (-model.posterior(X_train).mean).argmax() self.assertTrue((X_rnd == X_train[idx : idx + 1]).all(dim=-1).all()) # test acquisition function that has no model ff = FixedFeatureAcquisitionFunction(pm, d=2, columns=[0], values=[0]) # set X_baseline for testing purposes ff.X_baseline = X_train with warnings.catch_warnings(record=True) as w, settings.debug(True): X_rnd = sample_points_around_best( acq_function=ff, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # test constraints with NEHVI constraints = [lambda Y: Y[..., 0]] ref_point = torch.zeros(2, **tkwargs) # test cases when there are and are not any feasible points for any_feas in (True, False): Y_train = torch.stack( [ torch.linspace(-0.5, 0.5, X_train.shape[0], **tkwargs) if any_feas else torch.ones(X_train.shape[0], **tkwargs), X_train.sum(dim=-1), ], dim=-1, ) moo_model = MockModel(MockPosterior(mean=Y_train, samples=Y_train)) acqf = qNoisyExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, X_baseline=X_train, constraints=constraints, cache_root=False, ) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=0.0, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) # this should be true since sigma=0 # and we should only be returning feasible points violation = constraints[0](Y_train) neg_violation = -violation.clamp_min(0.0) feas = neg_violation == 0 eq_mask = (X_train.unsqueeze(1) == X_rnd.unsqueeze(0)).all(dim=-1) if feas.any(): # determine # create n_train x n_rnd tensor of booleans eq_mask = (X_train.unsqueeze(1) == X_rnd.unsqueeze(0)).all(dim=-1) # check that all X_rnd correspond to feasible points self.assertEqual(eq_mask[feas].sum(), 4) else: idcs = torch.topk(neg_violation, k=2).indices self.assertEqual(eq_mask[idcs].sum(), 4) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test that subset_dims is called if d>=21 X_train = 1 + torch.rand(20, 21, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True)) ) bounds = torch.ones(2, 21, **tkwargs) bounds[1] = 2 # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) with mock.patch( "botorch.optim.initializers.sample_perturbed_subset_dims", wraps=sample_perturbed_subset_dims, ) as mock_subset_dims: X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=5, sigma=1e-3, bounds=bounds ) self.assertTrue(X_rnd.shape, torch.Size([5, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) mock_subset_dims.assert_called_once() # test tiny prob_perturb to make sure we perturb at least one dimension X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=5, sigma=1e-3, bounds=bounds, prob_perturb=1e-8, ) self.assertTrue( ((X_rnd.unsqueeze(0) == X_train.unsqueeze(1)).all(dim=-1)).sum() == 0 )
def test_get_X_baseline(self): tkwargs = {"device": self.device} for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype X_train = torch.rand(20, 2, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True))) # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train[:2]) X = get_X_baseline(acq_function=acqf) self.assertTrue(torch.equal(X, acqf.X_baseline)) # test EI without X_baseline acqf = qExpectedImprovement(model, best_f=0.0) with warnings.catch_warnings( record=True) as w, settings.debug(True): X_rnd = get_X_baseline(acq_function=acqf, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # set train inputs model.train_inputs = (X_train, ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test that we fail back to train_inputs if X_baseline is an empty tensor acqf.register_buffer("X_baseline", X_train[:0]) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test acquisitipon function without X_baseline or model acqf = FixedFeatureAcquisitionFunction(acqf, d=2, columns=[0], values=[0]) with warnings.catch_warnings( record=True) as w, settings.debug(True): X_rnd = get_X_baseline(acq_function=acqf, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) Y_train = 2 * X_train[:2] + 1 moo_model = MockModel(MockPosterior(mean=Y_train, samples=Y_train)) ref_point = torch.zeros(2, **tkwargs) # test NEHVI with X_baseline acqf = qNoisyExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, X_baseline=X_train[:2], cache_root=False, ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, acqf.X_baseline)) # test qEHVI without train_inputs acqf = qExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, partitioning=FastNondominatedPartitioning( ref_point=ref_point, Y=Y_train, ), ) # test extracting train_inputs from model list GP model_list = ModelListGP( SingleTaskGP(X_train, Y_train[:, :1]), SingleTaskGP(X_train, Y_train[:, 1:]), ) acqf = qExpectedHypervolumeImprovement( model_list, ref_point=ref_point, partitioning=FastNondominatedPartitioning( ref_point=ref_point, Y=Y_train, ), ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test MESMO for which we need to use # `acqf.mo_model` batched_mo_model = SingleTaskGP(X_train, Y_train) acqf = qMultiObjectiveMaxValueEntropy( batched_mo_model, sample_pareto_frontiers=lambda model: torch.rand( 10, 2, **tkwargs), ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test that if there is an input transform that is applied # to the train_inputs when the model is in eval mode, we # extract the untransformed train_inputs model = SingleTaskGP(X_train, Y_train[:, :1], input_transform=Warp(indices=[0, 1])) model.eval() self.assertFalse(torch.equal(model.train_inputs[0], X_train)) acqf = qExpectedImprovement(model, best_f=0.0) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train))
def test_fit_gpytorch_model(self, cuda=False, optimizer=fit_gpytorch_scipy): options = {"disp": False, "maxiter": 5} for double in (False, True): mll = self._getModel(double=double, cuda=cuda) with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll = fit_gpytorch_model(mll, optimizer=optimizer, options=options, max_retries=1) if optimizer == fit_gpytorch_scipy: self.assertEqual(len(ws), 1) self.assertTrue(MAX_RETRY_MSG in str(ws[0].message)) model = mll.model # Make sure all of the parameters changed self.assertGreater(model.likelihood.raw_noise.abs().item(), 1e-3) self.assertLess(model.mean_module.constant.abs().item(), 0.1) self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3) # test overriding the default bounds with user supplied bounds mll = self._getModel(double=double, cuda=cuda) with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1, bounds={"likelihood.noise_covar.raw_noise": (1e-1, None)}, ) if optimizer == fit_gpytorch_scipy: self.assertEqual(len(ws), 1) self.assertTrue(MAX_RETRY_MSG in str(ws[0].message)) model = mll.model self.assertGreaterEqual(model.likelihood.raw_noise.abs().item(), 1e-1) self.assertLess(model.mean_module.constant.abs().item(), 0.1) self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3) # test tracking iterations mll = self._getModel(double=double, cuda=cuda) if optimizer is fit_gpytorch_torch: options["disp"] = True with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll, iterations = optimizer(mll, options=options, track_iterations=True) if optimizer == fit_gpytorch_scipy: self.assertEqual(len(ws), 1) self.assertTrue(MAX_ITER_MSG in str(ws[0].message)) self.assertEqual(len(iterations), options["maxiter"]) self.assertIsInstance(iterations[0], OptimizationIteration) # test extra param that does not affect loss options["disp"] = False mll = self._getModel(double=double, cuda=cuda) mll.register_parameter( "dummy_param", torch.nn.Parameter( torch.tensor( [5.0], dtype=torch.double if double else torch.float, device=torch.device("cuda" if cuda else "cpu"), )), ) with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll = fit_gpytorch_model(mll, optimizer=optimizer, options=options, max_retries=1) if optimizer == fit_gpytorch_scipy: self.assertEqual(len(ws), 1) self.assertTrue(MAX_RETRY_MSG in str(ws[0].message)) self.assertTrue(mll.dummy_param.grad is None) # test excluding a parameter mll = self._getModel(double=double, cuda=cuda) original_raw_noise = mll.model.likelihood.noise_covar.raw_noise.item( ) original_mean_module_constant = mll.model.mean_module.constant.item( ) options["exclude"] = [ "model.mean_module.constant", "likelihood.noise_covar.raw_noise", ] with warnings.catch_warnings( record=True) as ws, settings.debug(True): mll = fit_gpytorch_model(mll, optimizer=optimizer, options=options, max_retries=1) if optimizer == fit_gpytorch_scipy: self.assertEqual(len(ws), 1) self.assertTrue(MAX_RETRY_MSG in str(ws[0].message)) model = mll.model # Make excluded params did not change self.assertEqual(model.likelihood.noise_covar.raw_noise.item(), original_raw_noise) self.assertEqual(model.mean_module.constant.item(), original_mean_module_constant) # Make sure other params did change self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3)
def test_prune_inferior_points(self): for dtype in (torch.float, torch.double): X = torch.rand(3, 2, device=self.device, dtype=dtype) # the event shape is `q x t` = 3 x 1 samples = torch.tensor([[-1.0], [0.0], [1.0]], device=self.device, dtype=dtype) mm = MockModel(MockPosterior(samples=samples)) # test that a batched X raises errors with self.assertRaises(UnsupportedError): prune_inferior_points(model=mm, X=X.expand(2, 3, 2)) # test that a batched model raises errors (event shape is `q x t` = 3 x 1) mm2 = MockModel(MockPosterior(samples=samples.expand(2, 3, 1))) with self.assertRaises(UnsupportedError): prune_inferior_points(model=mm2, X=X) # test that invalid max_frac is checked properly with self.assertRaises(ValueError): prune_inferior_points(model=mm, X=X, max_frac=1.1) # test basic behaviour X_pruned = prune_inferior_points(model=mm, X=X) self.assertTrue(torch.equal(X_pruned, X[[-1]])) # test custom objective neg_id_obj = GenericMCObjective(lambda Y, X: -(Y.squeeze(-1))) X_pruned = prune_inferior_points(model=mm, X=X, objective=neg_id_obj) self.assertTrue(torch.equal(X_pruned, X[[0]])) # test non-repeated samples (requires mocking out MockPosterior's rsample) samples = torch.tensor( [[[3.0], [0.0], [0.0]], [[0.0], [2.0], [0.0]], [[0.0], [0.0], [1.0]]], device=self.device, dtype=dtype, ) with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points(model=mm, X=X) self.assertTrue(torch.equal(X_pruned, X)) # test max_frac limiting with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points(model=mm, X=X, max_frac=2 / 3) if self.device == torch.device("cuda"): # sorting has different order on cuda self.assertTrue( torch.equal(X_pruned, torch.stack([X[2], X[1]], dim=0))) else: self.assertTrue(torch.equal(X_pruned, X[:2])) # test that zero-probability is in fact pruned samples[2, 0, 0] = 10 with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points(model=mm, X=X) self.assertTrue(torch.equal(X_pruned, X[:2])) # test high-dim sampling with ExitStack() as es: mock_event_shape = es.enter_context( mock.patch( "botorch.utils.testing.MockPosterior.base_sample_shape", new_callable=mock.PropertyMock, )) mock_event_shape.return_value = torch.Size( [1, 1, torch.quasirandom.SobolEngine.MAXDIM + 1]) es.enter_context( mock.patch.object(MockPosterior, "rsample", return_value=samples)) mm = MockModel(MockPosterior(samples=samples)) with warnings.catch_warnings( record=True) as ws, settings.debug(True): prune_inferior_points(model=mm, X=X) self.assertTrue( issubclass(ws[-1].category, SamplingWarning))
def test_q_upper_confidence_bound_batch(self): # TODO: T41739913 Implement tests for all MCAcquisitionFunctions for dtype in (torch.float, torch.double): samples = torch.zeros(2, 2, 1, device=self.device, dtype=dtype) samples[0, 0, 0] = 1.0 mm = MockModel(MockPosterior(samples=samples)) # X is a dummy and unused b/c of mocking X = torch.zeros(1, 1, 1, device=self.device, dtype=dtype) # test batch mode sampler = IIDNormalSampler(num_samples=2) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) # test batch mode, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) # 1-dim batch self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) res = acqf(X.expand(2, 1, 1)) # 2-dim batch self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) # the base samples should have the batch dim collapsed self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X.expand(2, 1, 1)) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) # test batch mode, qmc, no resample sampler = SobolQMCNormalSampler(num_samples=2) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) # test batch mode, qmc, resample sampler = SobolQMCNormalSampler(num_samples=2, resample=True) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) # 1-dim batch self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertFalse(torch.equal(acqf.sampler.base_samples, bs)) res = acqf(X.expand(2, 1, 1)) # 2-dim batch self.assertEqual(res[0].item(), 1.0) self.assertEqual(res[1].item(), 0.0) # the base samples should have the batch dim collapsed self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X.expand(2, 1, 1)) 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, device=self.device, dtype=dtype, requires_grad=True ) 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))
def test_q_expected_improvement(self): for dtype in (torch.float, torch.double): # the event shape is `b x q x t` = 1 x 1 x 1 samples = torch.zeros(1, 1, 1, device=self.device, dtype=dtype) mm = MockModel(MockPosterior(samples=samples)) # X is `q x d` = 1 x 1. X is a dummy and unused b/c of mocking X = torch.zeros(1, 1, device=self.device, dtype=dtype) # basic test sampler = IIDNormalSampler(num_samples=2) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) # test shifting best_f value acqf = qExpectedImprovement(model=mm, best_f=-1, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 1.0) # test size verification of best_f with self.assertRaises(ValueError): qExpectedImprovement( model=mm, best_f=torch.zeros(2, device=self.device, dtype=dtype) ) # basic test, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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, device=self.device, dtype=dtype, requires_grad=True ) 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 bad objective type obj = ScalarizedObjective( weights=torch.rand(2, device=self.device, dtype=dtype) ) with self.assertRaises(UnsupportedError): qExpectedImprovement(model=mm, best_f=0, sampler=sampler, objective=obj)
def test_get_f_X_samples(self): tkwargs = {"device": self.device} for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype mean = torch.zeros(5, 1, **tkwargs) variance = torch.ones(5, 1, **tkwargs) mm = MockModel( MockPosterior(mean=mean, variance=variance, samples=torch.rand(5, 1, **tkwargs))) # basic test sampler = IIDNormalSampler(1) acqf = DummyCachedCholeskyAcqf(model=mm, sampler=sampler) acqf._setup(model=mm, sampler=sampler, cache_root=True) q = 3 baseline_L = torch.eye(5 - q, **tkwargs) acqf._baseline_L = baseline_L posterior = mm.posterior(torch.rand(5, 1, **tkwargs)) # basic test rv = torch.rand(1, 5, 1, **tkwargs) with mock.patch( "botorch.acquisition.cached_cholesky.sample_cached_cholesky", return_value=rv, ) as mock_sample_cached_cholesky: samples = acqf._get_f_X_samples(posterior=posterior, q_in=q) mock_sample_cached_cholesky.assert_called_once_with( posterior=posterior, baseline_L=acqf._baseline_L, q=q, base_samples=acqf.sampler.base_samples, sample_shape=acqf.sampler.sample_shape, ) self.assertTrue(torch.equal(rv, samples)) # test fall back when sampling from cached cholesky fails for error_cls in (NanError, NotPSDError): base_samples = torch.rand(1, 5, 1, **tkwargs) acqf.sampler.base_samples = base_samples acqf._baseline_L = baseline_L with mock.patch( "botorch.acquisition.cached_cholesky.sample_cached_cholesky", side_effect=error_cls, ) as mock_sample_cached_cholesky: with warnings.catch_warnings( record=True) as ws, settings.debug(True): samples = acqf._get_f_X_samples(posterior=posterior, q_in=q) mock_sample_cached_cholesky.assert_called_once_with( posterior=posterior, baseline_L=acqf._baseline_L, q=q, base_samples=base_samples, sample_shape=acqf.sampler.sample_shape, ) self.assertTrue( issubclass(ws[0].category, BotorchWarning)) self.assertTrue(samples.shape, torch.Size([1, q, 1])) # test HOGP hogp = HigherOrderGP(torch.zeros(2, 1), torch.zeros(2, 1, 1)).eval() acqf = DummyCachedCholeskyAcqf(model=hogp, sampler=sampler) acqf._setup(model=hogp, sampler=sampler, cache_root=True) mock_samples = torch.rand(5, 1, 1, **tkwargs) posterior = MockPosterior(mean=mean, variance=variance, samples=mock_samples) samples = acqf._get_f_X_samples(posterior=posterior, q_in=q) self.assertTrue(torch.equal(samples, mock_samples[2:].unsqueeze(0)))
def test_cache_root(self): sample_cached_path = ( "botorch.acquisition.cached_cholesky.sample_cached_cholesky") raw_state_dict = { "likelihood.noise_covar.raw_noise": torch.tensor([[0.0895], [0.2594]], dtype=torch.float64), "mean_module.constant": torch.tensor([[-0.4545], [-0.1285]], dtype=torch.float64), "covar_module.raw_outputscale": torch.tensor([1.4876, 1.4897], dtype=torch.float64), "covar_module.base_kernel.raw_lengthscale": torch.tensor([[[-0.7202, -0.2868]], [[-0.8794, -1.2877]]], dtype=torch.float64), } # test batched models (e.g. for MCMC) for train_batch_shape, m, dtype in product( (torch.Size([]), torch.Size([3])), (1, 2), (torch.float, torch.double)): state_dict = deepcopy(raw_state_dict) for k, v in state_dict.items(): if m == 1: v = v[0] if len(train_batch_shape) > 0: v = v.unsqueeze(0).expand(*train_batch_shape, *v.shape) state_dict[k] = v tkwargs = {"device": self.device, "dtype": dtype} if m == 2: objective = GenericMCObjective(lambda Y, X: Y.sum(dim=-1)) else: objective = None for k, v in state_dict.items(): state_dict[k] = v.to(**tkwargs) all_close_kwargs = ({ "atol": 1e-1, "rtol": 0.0, } if dtype == torch.float else { "atol": 1e-4, "rtol": 0.0 }) torch.manual_seed(1234) train_X = torch.rand(*train_batch_shape, 3, 2, **tkwargs) train_Y = ( torch.sin(train_X * 2 * pi) + torch.randn(*train_batch_shape, 3, 2, **tkwargs))[..., :m] train_Y = standardize(train_Y) model = SingleTaskGP( train_X, train_Y, ) if len(train_batch_shape) > 0: X_baseline = train_X[0] else: X_baseline = train_X model.load_state_dict(state_dict, strict=False) # test sampler with collapse_batch_dims=False sampler = IIDNormalSampler(5, seed=0, collapse_batch_dims=False) with self.assertRaises(UnsupportedError): qNoisyExpectedImprovement( model=model, X_baseline=X_baseline, sampler=sampler, objective=objective, prune_baseline=False, cache_root=True, ) sampler = IIDNormalSampler(5, seed=0) torch.manual_seed(0) acqf = qNoisyExpectedImprovement( model=model, X_baseline=X_baseline, sampler=sampler, objective=objective, prune_baseline=False, cache_root=True, ) orig_base_samples = acqf.base_sampler.base_samples.detach().clone() sampler2 = IIDNormalSampler(5, seed=0) sampler2.base_samples = orig_base_samples torch.manual_seed(0) acqf_no_cache = qNoisyExpectedImprovement( model=model, X_baseline=X_baseline, sampler=sampler2, objective=objective, prune_baseline=False, cache_root=False, ) for q, batch_shape in product( (1, 3), (torch.Size([]), torch.Size([3]), torch.Size([4, 3]))): test_X = (0.3 + 0.05 * torch.randn(*batch_shape, q, 2, **tkwargs) ).requires_grad_(True) with mock.patch( sample_cached_path, wraps=sample_cached_cholesky) as mock_sample_cached: torch.manual_seed(0) val = acqf(test_X) mock_sample_cached.assert_called_once() val.sum().backward() base_samples = acqf.sampler.base_samples.detach().clone() X_grad = test_X.grad.clone() test_X2 = test_X.detach().clone().requires_grad_(True) acqf_no_cache.sampler.base_samples = base_samples with mock.patch( sample_cached_path, wraps=sample_cached_cholesky) as mock_sample_cached: torch.manual_seed(0) val2 = acqf_no_cache(test_X2) mock_sample_cached.assert_not_called() self.assertTrue(torch.allclose(val, val2, **all_close_kwargs)) val2.sum().backward() self.assertTrue( torch.allclose(X_grad, test_X2.grad, **all_close_kwargs)) # test we fall back to standard sampling for # ill-conditioned covariances acqf._baseline_L = torch.zeros_like(acqf._baseline_L) with warnings.catch_warnings( record=True) as ws, settings.debug(True): with torch.no_grad(): acqf(test_X) self.assertEqual(len(ws), 1) self.assertTrue(issubclass(ws[-1].category, BotorchWarning))
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_q_upper_confidence_bound(self): for dtype in (torch.float, torch.double): # the event shape is `b x q x t` = 1 x 1 x 1 samples = torch.zeros(1, 1, 1, device=self.device, dtype=dtype) mm = MockModel(MockPosterior(samples=samples)) # X is `q x d` = 1 x 1. X is a dummy and unused b/c of mocking X = torch.zeros(1, 1, device=self.device, dtype=dtype) # basic test sampler = IIDNormalSampler(num_samples=2) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) # basic test, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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 = qUpperConfidenceBound(model=mm, beta=0.5, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) 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) mm._posterior._samples = mm._posterior._samples.expand(1, 2, 1) res = acqf(X) X2 = torch.zeros(1, 1, 1, device=self.device, dtype=dtype, requires_grad=True) 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))
def test_q_noisy_expected_improvement(self): for dtype in (torch.float, torch.double): # the event shape is `b x q x t` = 1 x 2 x 1 samples_noisy = torch.tensor([1.0, 0.0], device=self.device, dtype=dtype) samples_noisy = samples_noisy.view(1, 2, 1) # X_baseline is `q' x d` = 1 x 1 X_baseline = torch.zeros(1, 1, device=self.device, dtype=dtype) mm_noisy = MockModel(MockPosterior(samples=samples_noisy)) # X is `q x d` = 1 x 1 X = torch.zeros(1, 1, device=self.device, dtype=dtype) # basic test sampler = IIDNormalSampler(num_samples=2) acqf = qNoisyExpectedImprovement( model=mm_noisy, X_baseline=X_baseline, sampler=sampler ) res = acqf(X) self.assertEqual(res.item(), 1.0) # basic test, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qNoisyExpectedImprovement( model=mm_noisy, X_baseline=X_baseline, sampler=sampler ) res = acqf(X) self.assertEqual(res.item(), 1.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) # basic test, qmc, no resample sampler = SobolQMCNormalSampler(num_samples=2) acqf = qNoisyExpectedImprovement( model=mm_noisy, X_baseline=X_baseline, sampler=sampler ) res = acqf(X) self.assertEqual(res.item(), 1.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) 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, seed=12345) acqf = qNoisyExpectedImprovement( model=mm_noisy, X_baseline=X_baseline, sampler=sampler ) res = acqf(X) self.assertEqual(res.item(), 1.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 2, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertFalse(torch.equal(acqf.sampler.base_samples, bs)) # basic test for X_pending and warning sampler = SobolQMCNormalSampler(num_samples=2) samples_noisy_pending = torch.tensor( [1.0, 0.0, 0.0], device=self.device, dtype=dtype ) samples_noisy_pending = samples_noisy_pending.view(1, 3, 1) mm_noisy_pending = MockModel(MockPosterior(samples=samples_noisy_pending)) acqf = qNoisyExpectedImprovement( model=mm_noisy_pending, X_baseline=X_baseline, sampler=sampler ) 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, device=self.device, dtype=dtype, requires_grad=True ) 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))
def test_fit_gpytorch_model(self, optimizer=fit_gpytorch_scipy): options = {"disp": False, "maxiter": 5} for double in (False, True): mll = self._getModel(double=double) with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1 ) if optimizer == fit_gpytorch_scipy: self.assertTrue( any(issubclass(w.category, OptimizationWarning)) for w in ws ) self.assertEqual( sum(1 for w in ws if MAX_RETRY_MSG in str(w.message)), 1 ) model = mll.model # Make sure all of the parameters changed self.assertGreater(model.likelihood.raw_noise.abs().item(), 1e-3) self.assertLess(model.mean_module.constant.abs().item(), 0.1) self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1 ) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3) # test overriding the default bounds with user supplied bounds mll = self._getModel(double=double) with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1, bounds={"likelihood.noise_covar.raw_noise": (1e-1, None)}, ) if optimizer == fit_gpytorch_scipy: self.assertTrue( any(issubclass(w.category, OptimizationWarning)) for w in ws ) self.assertEqual( sum(1 for w in ws if MAX_RETRY_MSG in str(w.message)), 1 ) model = mll.model self.assertGreaterEqual(model.likelihood.raw_noise.abs().item(), 1e-1) self.assertLess(model.mean_module.constant.abs().item(), 0.1) self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1 ) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3) # test tracking iterations mll = self._getModel(double=double) if optimizer is fit_gpytorch_torch: options["disp"] = True with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll, info_dict = optimizer(mll, options=options, track_iterations=True) if optimizer == fit_gpytorch_scipy: self.assertEqual( sum(1 for w in ws if MAX_ITER_MSG in str(w.message)), 1 ) self.assertEqual(len(info_dict["iterations"]), options["maxiter"]) self.assertIsInstance(info_dict["iterations"][0], OptimizationIteration) self.assertTrue("fopt" in info_dict) self.assertTrue("wall_time" in info_dict) # Test different optimizer, for scipy optimizer, # because of different scipy OptimizeResult.message type if optimizer == fit_gpytorch_scipy: with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll, info_dict = optimizer( mll, options=options, track_iterations=False, method="slsqp" ) self.assertGreaterEqual(len(ws), 1) self.assertEqual(len(info_dict["iterations"]), 0) self.assertTrue("fopt" in info_dict) self.assertTrue("wall_time" in info_dict) # test extra param that does not affect loss options["disp"] = False mll = self._getModel(double=double) mll.register_parameter( "dummy_param", torch.nn.Parameter( torch.tensor( [5.0], dtype=torch.double if double else torch.float, device=self.device, ) ), ) with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1 ) if optimizer == fit_gpytorch_scipy: self.assertEqual( sum(1 for w in ws if MAX_RETRY_MSG in str(w.message)), 1 ) self.assertTrue(mll.dummy_param.grad is None) # test excluding a parameter mll = self._getModel(double=double) original_raw_noise = mll.model.likelihood.noise_covar.raw_noise.item() original_mean_module_constant = mll.model.mean_module.constant.item() options["exclude"] = [ "model.mean_module.constant", "likelihood.noise_covar.raw_noise", ] with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1 ) if optimizer == fit_gpytorch_scipy: self.assertTrue( any(issubclass(w.category, OptimizationWarning)) for w in ws ) self.assertEqual( sum(1 for w in ws if MAX_RETRY_MSG in str(w.message)), 1 ) model = mll.model # Make excluded params did not change self.assertEqual( model.likelihood.noise_covar.raw_noise.item(), original_raw_noise ) self.assertEqual( model.mean_module.constant.item(), original_mean_module_constant ) # Make sure other params did change self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1 ) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3) # test non-default setting for approximate MLL computation is_scipy = optimizer == fit_gpytorch_scipy mll = self._getModel(double=double) with warnings.catch_warnings(record=True) as ws, settings.debug(True): mll = fit_gpytorch_model( mll, optimizer=optimizer, options=options, max_retries=1, approx_mll=is_scipy, ) if is_scipy: self.assertTrue( any(issubclass(w.category, OptimizationWarning)) for w in ws ) self.assertEqual( sum(1 for w in ws if MAX_RETRY_MSG in str(w.message)), 1 ) model = mll.model # Make sure all of the parameters changed self.assertGreater(model.likelihood.raw_noise.abs().item(), 1e-3) self.assertLess(model.mean_module.constant.abs().item(), 0.1) self.assertGreater( model.covar_module.base_kernel.raw_lengthscale.abs().item(), 0.1 ) self.assertGreater(model.covar_module.raw_outputscale.abs().item(), 1e-3)
def test_prune_inferior_points_multi_objective(self): tkwargs = {"device": self.device} for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype X = torch.rand(3, 2, **tkwargs) ref_point = torch.tensor([0.25, 0.25], **tkwargs) # the event shape is `q x m` = 3 x 2 samples = torch.tensor([[1.0, 2.0], [2.0, 1.0], [3.0, 4.0]], **tkwargs) mm = MockModel(MockPosterior(samples=samples)) # test that a batched X raises errors with self.assertRaises(UnsupportedError): prune_inferior_points_multi_objective( model=mm, X=X.expand(2, 3, 2), ref_point=ref_point ) # test that a batched model raises errors (event shape is `q x m` = 3 x m) mm2 = MockModel(MockPosterior(samples=samples.expand(2, 3, 2))) with self.assertRaises(UnsupportedError): prune_inferior_points_multi_objective( model=mm2, X=X, ref_point=ref_point ) # test that invalid max_frac is checked properly with self.assertRaises(ValueError): prune_inferior_points_multi_objective( model=mm, X=X, max_frac=1.1, ref_point=ref_point ) # test basic behaviour X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point ) self.assertTrue(torch.equal(X_pruned, X[[-1]])) # test unstd objective unstd_obj = UnstandardizeMCMultiOutputObjective( Y_mean=samples.mean(dim=0), Y_std=samples.std(dim=0), outcomes=[0, 1] ) X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point, objective=unstd_obj ) self.assertTrue(torch.equal(X_pruned, X[[-1]])) # test constraints samples_constrained = torch.tensor( [[1.0, 2.0, -1.0], [2.0, 1.0, -1.0], [3.0, 4.0, 1.0]], **tkwargs ) mm_constrained = MockModel(MockPosterior(samples=samples_constrained)) X_pruned = prune_inferior_points_multi_objective( model=mm_constrained, X=X, ref_point=ref_point, objective=unstd_obj, constraints=[lambda Y: Y[..., -1]], ) self.assertTrue(torch.equal(X_pruned, X[:2])) # test non-repeated samples (requires mocking out MockPosterior's rsample) samples = torch.tensor( [[[3.0], [0.0], [0.0]], [[0.0], [2.0], [0.0]], [[0.0], [0.0], [1.0]]], device=self.device, dtype=dtype, ) with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point ) self.assertTrue(torch.equal(X_pruned, X)) # test max_frac limiting with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point, max_frac=2 / 3 ) if self.device.type == "cuda": # sorting has different order on cuda self.assertTrue(torch.equal(X_pruned, torch.stack([X[2], X[1]], dim=0))) else: self.assertTrue(torch.equal(X_pruned, X[:2])) # test that zero-probability is in fact pruned samples[2, 0, 0] = 10 with mock.patch.object(MockPosterior, "rsample", return_value=samples): mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point ) self.assertTrue(torch.equal(X_pruned, X[:2])) # test high-dim sampling with ExitStack() as es: mock_event_shape = es.enter_context( mock.patch( "botorch.utils.testing.MockPosterior.event_shape", new_callable=mock.PropertyMock, ) ) mock_event_shape.return_value = torch.Size( [1, 1, torch.quasirandom.SobolEngine.MAXDIM + 1] ) es.enter_context( mock.patch.object(MockPosterior, "rsample", return_value=samples) ) mm = MockModel(MockPosterior(samples=samples)) with warnings.catch_warnings(record=True) as ws, settings.debug(True): prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point ) self.assertTrue(issubclass(ws[-1].category, SamplingWarning)) # test marginalize_dim and constraints samples = torch.tensor([[1.0, 2.0], [2.0, 1.0], [3.0, 4.0]], **tkwargs) samples = samples.unsqueeze(-3).expand( *samples.shape[:-2], 2, *samples.shape[-2:], ) mm = MockModel(MockPosterior(samples=samples)) X_pruned = prune_inferior_points_multi_objective( model=mm, X=X, ref_point=ref_point, objective=unstd_obj, constraints=[lambda Y: Y[..., -1] - 3.0], marginalize_dim=-3, ) self.assertTrue(torch.equal(X_pruned, X[:2]))