|
16 | 16 | qMultiFidelityKnowledgeGradient, |
17 | 17 | ) |
18 | 18 | from botorch.acquisition.monte_carlo import qSimpleRegret |
19 | | -from botorch.acquisition.objective import GenericMCObjective |
| 19 | +from botorch.acquisition.objective import GenericMCObjective, ScalarizedObjective |
20 | 20 | from botorch.exceptions.errors import UnsupportedError |
| 21 | +from botorch.posteriors.gpytorch import GPyTorchPosterior |
21 | 22 | from botorch.sampling.samplers import IIDNormalSampler, SobolQMCNormalSampler |
22 | 23 | from botorch.utils.testing import BotorchTestCase, MockModel, MockPosterior |
| 24 | +from gpytorch.distributions import MultitaskMultivariateNormal |
23 | 25 |
|
24 | 26 |
|
25 | 27 | NO = "botorch.utils.testing.MockModel.num_outputs" |
@@ -92,6 +94,20 @@ def test_initialize_q_knowledge_gradient(self): |
92 | 94 | self.assertIsNone(qKG.X_pending) |
93 | 95 | self.assertTrue(torch.equal(qKG.current_value, current_value)) |
94 | 96 | self.assertEqual(qKG.get_augmented_q_batch_size(q=3), 8 + 3) |
| 97 | + # test construction with non-MC objective (ScalarizedObjective) |
| 98 | + qKG_s = qKnowledgeGradient( |
| 99 | + model=mm, |
| 100 | + num_fantasies=16, |
| 101 | + sampler=sampler, |
| 102 | + objective=ScalarizedObjective(weights=torch.rand(2)), |
| 103 | + ) |
| 104 | + self.assertIsNone(qKG_s.inner_sampler) |
| 105 | + self.assertIsInstance(qKG_s.objective, ScalarizedObjective) |
| 106 | + # test error if no objective and multi-output model |
| 107 | + mean2 = torch.zeros(1, 2, device=self.device, dtype=dtype) |
| 108 | + mm2 = MockModel(MockPosterior(mean=mean2)) |
| 109 | + with self.assertRaises(UnsupportedError): |
| 110 | + qKnowledgeGradient(model=mm2) |
95 | 111 |
|
96 | 112 | def test_evaluate_q_knowledge_gradient(self): |
97 | 113 | for dtype in (torch.float, torch.double): |
@@ -172,6 +188,30 @@ def test_evaluate_q_knowledge_gradient(self): |
172 | 188 | self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1])) |
173 | 189 | self.assertTrue(torch.allclose(val, objective(samples).mean(), atol=1e-4)) |
174 | 190 | self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :])) |
| 191 | + # test non-MC objective (ScalarizedObjective) |
| 192 | + weights = torch.rand(2, device=self.device, dtype=dtype) |
| 193 | + objective = ScalarizedObjective(weights=weights) |
| 194 | + mean = torch.tensor([1.0, 0.5], device=self.device, dtype=dtype).expand( |
| 195 | + n_f, 1, 2 |
| 196 | + ) |
| 197 | + cov = torch.tensor( |
| 198 | + [[1.0, 0.1], [0.1, 0.5]], device=self.device, dtype=dtype |
| 199 | + ).expand(n_f, 2, 2) |
| 200 | + posterior = GPyTorchPosterior(MultitaskMultivariateNormal(mean, cov)) |
| 201 | + mfm = MockModel(posterior) |
| 202 | + with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: |
| 203 | + with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: |
| 204 | + mock_num_outputs.return_value = 2 |
| 205 | + mm = MockModel(None) |
| 206 | + qKG = qKnowledgeGradient( |
| 207 | + model=mm, num_fantasies=n_f, objective=objective |
| 208 | + ) |
| 209 | + val = qKG(X) |
| 210 | + patch_f.assert_called_once() |
| 211 | + cargs, ckwargs = patch_f.call_args |
| 212 | + self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1])) |
| 213 | + val_expected = (mean * weights).sum(-1).mean(0) |
| 214 | + self.assertTrue(torch.allclose(val, val_expected)) |
175 | 215 |
|
176 | 216 |
|
177 | 217 | class TestQMultiFidelityKnowledgeGradient(BotorchTestCase): |
|
0 commit comments