xopt 2.2.1__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {xopt-2.2.1/xopt.egg-info → xopt-2.2.3}/PKG-INFO +1 -1
  2. {xopt-2.2.1 → xopt-2.2.3}/requirements.txt +1 -1
  3. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_bax.py +33 -8
  4. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_bayesian_exploration.py +10 -0
  5. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_bayesian_generator.py +9 -0
  6. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_model_constructor.py +34 -0
  7. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_utils.py +1 -1
  8. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/test_neldermead.py +2 -2
  9. {xopt-2.2.1 → xopt-2.2.3}/tests/test_evaluator.py +28 -1
  10. {xopt-2.2.1 → xopt-2.2.3}/tests/test_vocs.py +49 -6
  11. {xopt-2.2.1 → xopt-2.2.3}/xopt/_version.py +3 -3
  12. {xopt-2.2.1 → xopt-2.2.3}/xopt/asynchronous.py +2 -1
  13. {xopt-2.2.1 → xopt-2.2.3}/xopt/base.py +12 -4
  14. {xopt-2.2.1 → xopt-2.2.3}/xopt/evaluator.py +3 -1
  15. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bax/acquisition.py +6 -3
  16. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bax/algorithms.py +1 -1
  17. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bax_generator.py +9 -2
  18. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bayesian_exploration.py +8 -11
  19. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bayesian_generator.py +17 -3
  20. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/constrained_acquisition.py +4 -3
  21. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/heteroskedastic.py +2 -1
  22. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/identity.py +5 -2
  23. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/proximal.py +8 -4
  24. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/expected_improvement.py +1 -2
  25. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/mggpo.py +1 -1
  26. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/mobo.py +1 -1
  27. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/models/prior_mean.py +2 -1
  28. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/models/standard.py +39 -12
  29. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/multi_fidelity.py +1 -1
  30. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/objectives.py +8 -22
  31. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/turbo.py +2 -2
  32. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/upper_confidence_bound.py +1 -3
  33. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/utils.py +20 -7
  34. xopt-2.2.3/xopt/generators/bayesian/visualize.py +1091 -0
  35. {xopt-2.2.1 → xopt-2.2.3}/xopt/pydantic.py +4 -2
  36. xopt-2.2.3/xopt/resources/test_functions/sphere_100.py +13 -0
  37. xopt-2.2.3/xopt/resources/test_functions/sphere_50.py +13 -0
  38. {xopt-2.2.1 → xopt-2.2.3}/xopt/utils.py +5 -1
  39. {xopt-2.2.1 → xopt-2.2.3}/xopt/vocs.py +139 -60
  40. {xopt-2.2.1 → xopt-2.2.3/xopt.egg-info}/PKG-INFO +1 -1
  41. {xopt-2.2.1 → xopt-2.2.3}/xopt.egg-info/SOURCES.txt +2 -0
  42. {xopt-2.2.1 → xopt-2.2.3}/xopt.egg-info/requires.txt +1 -1
  43. xopt-2.2.1/xopt/generators/bayesian/visualize.py +0 -396
  44. {xopt-2.2.1 → xopt-2.2.3}/LICENSE +0 -0
  45. {xopt-2.2.1 → xopt-2.2.3}/MANIFEST.in +0 -0
  46. {xopt-2.2.1 → xopt-2.2.3}/README.md +0 -0
  47. {xopt-2.2.1 → xopt-2.2.3}/benchmarks/__init__.py +0 -0
  48. {xopt-2.2.1 → xopt-2.2.3}/benchmarks/test_mo.py +0 -0
  49. {xopt-2.2.1 → xopt-2.2.3}/setup.cfg +0 -0
  50. {xopt-2.2.1 → xopt-2.2.3}/setup.py +0 -0
  51. {xopt-2.2.1 → xopt-2.2.3}/tests/__init__.py +0 -0
  52. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/__init__.py +0 -0
  53. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/__init__.py +0 -0
  54. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_constraints.py +0 -0
  55. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_custom_model.py +0 -0
  56. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_expected_improvement.py +0 -0
  57. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_hessian_kernel.py +0 -0
  58. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_high_level.py +0 -0
  59. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_mggpo.py +0 -0
  60. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_mobo.py +0 -0
  61. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_multi_fidelity.py +0 -0
  62. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_time_dependent_bo.py +0 -0
  63. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_turbo.py +0 -0
  64. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/bayesian/test_upper_confidence_bound.py +0 -0
  65. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/test_extremum_seeking.py +0 -0
  66. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/test_latin_hypercube.py +0 -0
  67. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/test_random.py +0 -0
  68. {xopt-2.2.1 → xopt-2.2.3}/tests/generators/test_rcds.py +0 -0
  69. {xopt-2.2.1 → xopt-2.2.3}/tests/test_generator.py +0 -0
  70. {xopt-2.2.1 → xopt-2.2.3}/tests/test_io.py +0 -0
  71. {xopt-2.2.1 → xopt-2.2.3}/tests/test_mpi.py +0 -0
  72. {xopt-2.2.1 → xopt-2.2.3}/tests/test_numerical_optimizer.py +0 -0
  73. {xopt-2.2.1 → xopt-2.2.3}/tests/test_perf.py +0 -0
  74. {xopt-2.2.1 → xopt-2.2.3}/tests/test_pydantic.py +0 -0
  75. {xopt-2.2.1 → xopt-2.2.3}/tests/test_utils.py +0 -0
  76. {xopt-2.2.1 → xopt-2.2.3}/tests/test_xopt.py +0 -0
  77. {xopt-2.2.1 → xopt-2.2.3}/versioneer.py +0 -0
  78. {xopt-2.2.1 → xopt-2.2.3}/xopt/__init__.py +0 -0
  79. {xopt-2.2.1 → xopt-2.2.3}/xopt/errors.py +0 -0
  80. {xopt-2.2.1 → xopt-2.2.3}/xopt/generator.py +0 -0
  81. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/__init__.py +0 -0
  82. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/__init__.py +0 -0
  83. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/base_model.py +0 -0
  84. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/bax/__init__.py +0 -0
  85. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/__init__.py +0 -0
  86. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/hessian_kernel.py +0 -0
  87. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/log_acquisition_function.py +0 -0
  88. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/custom_botorch/multi_fidelity.py +0 -0
  89. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/models/__init__.py +0 -0
  90. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/models/time_dependent.py +0 -0
  91. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/bayesian/time_dependent.py +0 -0
  92. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/es/__init__.py +0 -0
  93. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/es/extremumseeking.py +0 -0
  94. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/ga/__init__.py +0 -0
  95. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/ga/cnsga.py +0 -0
  96. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/ga/deap_creator.py +0 -0
  97. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/ga/deap_fitness_with_constraints.py +0 -0
  98. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/ga/fitness_with_constraints.py +0 -0
  99. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/random.py +0 -0
  100. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/rcds/__init__.py +0 -0
  101. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/rcds/rcds.py +0 -0
  102. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/scipy/__init__.py +0 -0
  103. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/scipy/latin_hypercube.py +0 -0
  104. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/scipy/neldermead.py +0 -0
  105. {xopt-2.2.1 → xopt-2.2.3}/xopt/generators/utils.py +0 -0
  106. {xopt-2.2.1 → xopt-2.2.3}/xopt/log.py +0 -0
  107. {xopt-2.2.1 → xopt-2.2.3}/xopt/mpi/__init__.py +0 -0
  108. {xopt-2.2.1 → xopt-2.2.3}/xopt/mpi/run.py +0 -0
  109. {xopt-2.2.1 → xopt-2.2.3}/xopt/numerical_optimizer.py +0 -0
  110. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/__init__.py +0 -0
  111. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/benchmarking.py +0 -0
  112. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/__init__.py +0 -0
  113. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/ackley_20.py +0 -0
  114. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/modified_tnk.py +0 -0
  115. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/multi_objective.py +0 -0
  116. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/problem.py +0 -0
  117. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/rosenbrock.py +0 -0
  118. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/sinusoid_1d.py +0 -0
  119. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/sphere_20.py +0 -0
  120. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/test_functions/tnk.py +0 -0
  121. {xopt-2.2.1 → xopt-2.2.3}/xopt/resources/testing.py +0 -0
  122. {xopt-2.2.1 → xopt-2.2.3}/xopt.egg-info/dependency_links.txt +0 -0
  123. {xopt-2.2.1 → xopt-2.2.3}/xopt.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xopt
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Home-page: https://github.com/ChristopherMayes/xopt
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -2,7 +2,7 @@ deap
2
2
  numpy
3
3
  pyyaml
4
4
  torch
5
- botorch>=0.9.2
5
+ botorch>=0.9.2,<=0.10.0
6
6
  gpytorch
7
7
  pandas
8
8
  pydantic>=2.3
@@ -2,11 +2,15 @@ import pickle
2
2
  from copy import deepcopy
3
3
  from unittest.mock import patch
4
4
 
5
+ import pytest
6
+
5
7
  import torch
6
8
  from botorch.models import SingleTaskGP
7
9
  from botorch.models.model import ModelList
8
10
  from botorch.models.transforms import Normalize, Standardize
9
11
 
12
+ from pydantic import ValidationError
13
+
10
14
  from xopt.base import Xopt
11
15
  from xopt.evaluator import Evaluator
12
16
  from xopt.generators.bayesian.bax.algorithms import (
@@ -21,9 +25,12 @@ from xopt.resources.testing import TEST_VOCS_BASE, TEST_VOCS_DATA, xtest_callabl
21
25
  class TestBaxGenerator:
22
26
  @patch.multiple(Algorithm, __abstractmethods__=set())
23
27
  def test_init(self):
28
+ test_vocs = deepcopy(TEST_VOCS_BASE)
29
+ test_vocs.objectives = {}
30
+ test_vocs.observables = ["y1"]
24
31
  alg = Algorithm()
25
32
  bax_gen = BaxGenerator(
26
- vocs=deepcopy(TEST_VOCS_BASE),
33
+ vocs=test_vocs,
27
34
  algorithm=alg,
28
35
  )
29
36
  bax_gen.model_dump()
@@ -110,6 +117,8 @@ class TestBaxGenerator:
110
117
 
111
118
  # test w/o constraints
112
119
  test_vocs = deepcopy(TEST_VOCS_BASE)
120
+ test_vocs.objectives = {}
121
+ test_vocs.observables = ["y1"]
113
122
  test_vocs.constraints = {}
114
123
  gen = BaxGenerator(
115
124
  vocs=test_vocs,
@@ -123,6 +132,8 @@ class TestBaxGenerator:
123
132
 
124
133
  # test w/ constraints
125
134
  test_vocs = deepcopy(TEST_VOCS_BASE)
135
+ test_vocs.objectives = {}
136
+ test_vocs.observables = ["y1"]
126
137
  gen = BaxGenerator(
127
138
  vocs=test_vocs,
128
139
  algorithm=alg,
@@ -135,8 +146,11 @@ class TestBaxGenerator:
135
146
 
136
147
  def test_cuda(self):
137
148
  alg = GridMinimize()
149
+ test_vocs = deepcopy(TEST_VOCS_BASE)
150
+ test_vocs.objectives = {}
151
+ test_vocs.observables = ["y1"]
138
152
  gen = BaxGenerator(
139
- vocs=TEST_VOCS_BASE,
153
+ vocs=test_vocs,
140
154
  algorithm=alg,
141
155
  )
142
156
 
@@ -151,13 +165,16 @@ class TestBaxGenerator:
151
165
  evaluator = Evaluator(function=xtest_callable)
152
166
  alg = GridMinimize()
153
167
 
168
+ test_vocs = deepcopy(TEST_VOCS_BASE)
169
+ test_vocs.objectives = {}
170
+ test_vocs.observables = ["y1"]
154
171
  gen = BaxGenerator(
155
- vocs=TEST_VOCS_BASE,
172
+ vocs=test_vocs,
156
173
  algorithm=alg,
157
174
  )
158
175
  gen.numerical_optimizer.n_restarts = 1
159
176
 
160
- xopt = Xopt(generator=gen, evaluator=evaluator, vocs=TEST_VOCS_BASE)
177
+ xopt = Xopt(generator=gen, evaluator=evaluator, vocs=test_vocs)
161
178
 
162
179
  # initialize with single initial candidate
163
180
  xopt.random_evaluate(3)
@@ -167,12 +184,13 @@ class TestBaxGenerator:
167
184
  evaluator = Evaluator(function=xtest_callable)
168
185
  alg = GridMinimize()
169
186
 
170
- gen = BaxGenerator(
171
- vocs=TEST_VOCS_BASE, algorithm=alg, algorithm_results_file="test"
172
- )
187
+ test_vocs = deepcopy(TEST_VOCS_BASE)
188
+ test_vocs.objectives = {}
189
+ test_vocs.observables = ["y1"]
190
+ gen = BaxGenerator(vocs=test_vocs, algorithm=alg, algorithm_results_file="test")
173
191
  gen.numerical_optimizer.n_restarts = 1
174
192
 
175
- xopt = Xopt(generator=gen, evaluator=evaluator, vocs=TEST_VOCS_BASE)
193
+ xopt = Xopt(generator=gen, evaluator=evaluator, vocs=test_vocs)
176
194
 
177
195
  # initialize with single initial candidate
178
196
  xopt.random_evaluate(3)
@@ -191,3 +209,10 @@ class TestBaxGenerator:
191
209
 
192
210
  os.remove("test_1.pkl")
193
211
  os.remove("test_2.pkl")
212
+
213
+ def test_vocs_validation(self):
214
+ test_vocs = deepcopy(TEST_VOCS_BASE)
215
+ alg = GridMinimize()
216
+
217
+ with pytest.raises(ValidationError):
218
+ BaxGenerator(vocs=test_vocs, algorithm=alg)
@@ -1,5 +1,9 @@
1
1
  from copy import deepcopy
2
2
 
3
+ import pytest
4
+
5
+ from pydantic import ValidationError
6
+
3
7
  from xopt.base import Xopt
4
8
  from xopt.evaluator import Evaluator
5
9
  from xopt.generators.bayesian.bayesian_exploration import BayesianExplorationGenerator
@@ -76,3 +80,9 @@ class TestBayesianExplorationGenerator:
76
80
  X.step()
77
81
  X.step()
78
82
  assert len(X.data) == 20
83
+
84
+ def test_vocs_validation(self):
85
+ test_vocs = deepcopy(TEST_VOCS_BASE)
86
+
87
+ with pytest.raises(ValidationError):
88
+ BayesianExplorationGenerator(vocs=test_vocs)
@@ -239,3 +239,12 @@ class TestBayesianGenerator(TestCase):
239
239
  model.models[0].input_transform.bounds,
240
240
  torch.tensor(((0, 0), (1, 10))).double(),
241
241
  )
242
+
243
+ # test bad fixed feature name
244
+ gen = BayesianGenerator(vocs=TEST_VOCS_BASE)
245
+ gen.fixed_features = {"bad_name": 3.0}
246
+ data = deepcopy(TEST_VOCS_DATA)
247
+ gen.add_data(data)
248
+
249
+ with pytest.raises(KeyError):
250
+ gen.train_model()
@@ -13,6 +13,7 @@ from botorch.models.transforms import Normalize, Standardize
13
13
  from gpytorch import ExactMarginalLogLikelihood
14
14
  from gpytorch.kernels import PeriodicKernel, PolynomialKernel, ScaleKernel
15
15
  from gpytorch.likelihoods import GaussianLikelihood
16
+ from gpytorch.means import ConstantMean
16
17
  from gpytorch.priors import GammaPrior
17
18
  from pydantic import ValidationError
18
19
 
@@ -437,6 +438,23 @@ class TestModelConstructor:
437
438
  assert torch.allclose(generated_pred, benchmark_pred, rtol=1e-3)
438
439
  assert ~torch.allclose(generated_pred, old_prediction, rtol=1e-3)
439
440
 
441
+ def test_unused_modules_warning(self):
442
+ test_vocs = deepcopy(TEST_VOCS_BASE)
443
+ test_data = deepcopy(TEST_VOCS_DATA)
444
+
445
+ # test unused covariance or mean module
446
+ kwargs_covar = {"covar_modules": {"faulty_output_name": PeriodicKernel()}}
447
+ kwargs_mean = {"mean_modules": {"faulty_output_name": ConstantMean()}}
448
+
449
+ for kwargs in [kwargs_covar, kwargs_mean]:
450
+ gp_constructor = StandardModelConstructor(**kwargs)
451
+ generator = ExpectedImprovementGenerator(
452
+ vocs=test_vocs, gp_constructor=gp_constructor
453
+ )
454
+ generator.add_data(test_data)
455
+ with pytest.warns(UserWarning):
456
+ _ = generator.train_model()
457
+
440
458
  def test_heteroskedastic(self):
441
459
  test_data = deepcopy(TEST_VOCS_DATA)
442
460
  test_vocs = deepcopy(TEST_VOCS_BASE)
@@ -487,6 +505,22 @@ class TestModelConstructor:
487
505
  posterior.variance[..., 0].flatten(), bposterior.variance.flatten()
488
506
  )
489
507
 
508
+ def test_custom_noise_prior(self):
509
+ test_data = deepcopy(TEST_VOCS_DATA)
510
+ test_vocs = deepcopy(TEST_VOCS_BASE)
511
+
512
+ noise_prior = GammaPrior(1.0, 1000.0)
513
+
514
+ gp_constructor = StandardModelConstructor(custom_noise_prior=noise_prior)
515
+ model = gp_constructor.build_model_from_vocs(test_vocs, test_data)
516
+
517
+ # check if created models have the correct noise priors
518
+ assert model.models[0].likelihood.noise_covar.noise_prior.rate == 1000.0
519
+ assert model.models[0].likelihood.noise_covar.noise_prior.concentration == 1.0
520
+
521
+ assert model.models[1].likelihood.noise_covar.noise_prior.rate == 1000.0
522
+ assert model.models[1].likelihood.noise_covar.noise_prior.concentration == 1.0
523
+
490
524
  @pytest.fixture(autouse=True)
491
525
  def clean_up(self):
492
526
  yield
@@ -10,7 +10,7 @@ class TestUtils:
10
10
  def test_mobo_objective(self):
11
11
  test_vocs_copy = deepcopy(TEST_VOCS_BASE)
12
12
  test_vocs_copy.objectives["y2"] = "MAXIMIZE"
13
- obj = create_mobo_objective(test_vocs_copy, tkwargs={})
13
+ obj = create_mobo_objective(test_vocs_copy)
14
14
 
15
15
  # test large sample shape
16
16
  test_samples = torch.randn(3, 4, 5, 3).double()
@@ -138,7 +138,7 @@ class TestNelderMeadGenerator:
138
138
  data = X.vocs.variable_data(X.data).to_numpy()
139
139
  assert np.array_equal(data, scipy_data)
140
140
 
141
- idx, best = X.vocs.select_best(X.data)
141
+ idx, best, _ = X.vocs.select_best(X.data)
142
142
  xbest = X.vocs.variable_data(X.data.loc[idx, :]).to_numpy().flatten()
143
143
  assert np.array_equal(
144
144
  xbest, result.x
@@ -216,7 +216,7 @@ class TestNelderMeadGenerator:
216
216
  assert data.shape == scipy_data.shape
217
217
  assert np.allclose(data, scipy_data, rtol=0, atol=1e-10)
218
218
 
219
- idx, best = X.vocs.select_best(X.data)
219
+ idx, best, _ = X.vocs.select_best(X.data)
220
220
  xbest = X.vocs.variable_data(X.data.loc[idx, :]).to_numpy().flatten()
221
221
  assert np.array_equal(
222
222
  xbest, result.x
@@ -3,7 +3,8 @@ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
 
6
- from xopt import Evaluator
6
+ from xopt import Evaluator, Xopt
7
+ from xopt.generators import RandomGenerator
7
8
  from xopt.vocs import VOCS
8
9
 
9
10
 
@@ -135,3 +136,29 @@ class TestEvaluator:
135
136
  executor = ProcessPoolExecutor(max_workers=MAX_WORKERS)
136
137
  ev = Evaluator(function=self.f, executor=executor, max_workers=MAX_WORKERS)
137
138
  ev.evaluate_data(in10)
139
+
140
+ def test_evaluate_return_types(self):
141
+ def a(input):
142
+ return {"f": 1.0}
143
+
144
+ def b(input):
145
+ return {"f": [1.0]}
146
+
147
+ def c(input):
148
+ return {"f": np.array(1.0)}
149
+
150
+ def d(input):
151
+ return {"f": [1.0, 1.0]}
152
+
153
+ def e(input):
154
+ return {"f": np.ones(3)}
155
+
156
+ vocs = VOCS(variables={"x": [0, 1]}, objectives={"f": "MINIMIZE"})
157
+
158
+ for func in [a, b, c, d, e]:
159
+ evaluator = Evaluator(function=func)
160
+ generator = RandomGenerator(vocs=vocs)
161
+
162
+ X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)
163
+
164
+ X.random_evaluate(5)
@@ -3,6 +3,7 @@ from copy import deepcopy
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
  import pytest
6
+ from pydantic import ValidationError
6
7
 
7
8
  from xopt.resources.testing import TEST_VOCS_BASE, TEST_VOCS_DATA
8
9
  from xopt.vocs import ObjectiveEnum, VOCS
@@ -26,6 +27,36 @@ class TestVOCS(object):
26
27
  variables={"x": [0, 1]},
27
28
  )
28
29
 
30
+ def test_output_names(self):
31
+ test_vocs = VOCS(
32
+ variables={"x": [0, 1]},
33
+ objectives={"y1": "MINIMIZE"},
34
+ constraints={"c1": ["GREATER_THAN", 0], "c2": ["LESS_THAN", 0]},
35
+ )
36
+ assert test_vocs.output_names == ["y1", "c1", "c2"]
37
+
38
+ def test_constraint_specification(self):
39
+ good_constraint_list = [
40
+ ["LESS_THAN", 0],
41
+ ["GREATER_THAN", 0],
42
+ ["less_than", 0],
43
+ ["greater_than", 0],
44
+ ]
45
+
46
+ for ele in good_constraint_list:
47
+ VOCS(variables={"x": [0, 1]}, constraints={"c": ele})
48
+
49
+ bad_constraint_list = [
50
+ ["LESS_THAN"],
51
+ ["GREATER_TAN", 0],
52
+ [0, "less_than"],
53
+ ["greater_than", "ahc"],
54
+ ]
55
+
56
+ for ele in bad_constraint_list:
57
+ with pytest.raises(ValidationError):
58
+ VOCS(variables={"x": [0, 1]}, constraints={"c": ele})
59
+
29
60
  def test_from_yaml(self):
30
61
  Y = """
31
62
  variables:
@@ -63,7 +94,11 @@ class TestVOCS(object):
63
94
  data = pd.DataFrame(vocs.random_inputs(n_samples))
64
95
  assert data.shape == (n_samples, vocs.n_inputs)
65
96
 
66
- TEST_VOCS_BASE.random_inputs(5, include_constants=False)
97
+ test_inputs = TEST_VOCS_BASE.random_inputs(5, include_constants=False)
98
+ assert len(test_inputs) == 5
99
+
100
+ test_inputs = TEST_VOCS_BASE.random_inputs()
101
+ assert isinstance(test_inputs[0]["x1"], float)
67
102
 
68
103
  def test_serialization(self):
69
104
  vocs = deepcopy(TEST_VOCS_BASE)
@@ -181,32 +216,40 @@ class TestVOCS(object):
181
216
 
182
217
  # test maximization
183
218
  vocs.objectives[vocs.objective_names[0]] = "MAXIMIZE"
184
- idx, val = vocs.select_best(test_data)
219
+ idx, val, _ = vocs.select_best(test_data)
185
220
  assert idx == [2]
186
221
  assert val == [1.0]
187
222
 
188
223
  vocs.constraints = {}
189
- idx, val = vocs.select_best(test_data)
224
+ idx, val, _ = vocs.select_best(test_data)
190
225
  assert idx == [3]
191
226
  assert val == [1.5]
192
227
 
193
228
  # test returning multiple best values -- sorted by best value
194
- idx, val = vocs.select_best(test_data, 2)
229
+ idx, val, _ = vocs.select_best(test_data, 2)
195
230
  assert np.allclose(idx, np.array([3, 2]))
196
231
  assert np.allclose(val, np.array([1.5, 1.0]))
197
232
 
198
233
  # test minimization
199
234
  vocs.objectives[vocs.objective_names[0]] = "MINIMIZE"
200
235
  vocs.constraints = {"c1": ["GREATER_THAN", 0.5]}
201
- idx, val = vocs.select_best(test_data)
236
+ idx, val, _ = vocs.select_best(test_data)
202
237
  assert idx == [0]
203
238
  assert val == [0.5]
204
239
 
205
240
  vocs.constraints = {}
206
- idx, val = vocs.select_best(test_data)
241
+ idx, val, _ = vocs.select_best(test_data)
207
242
  assert idx == 1
208
243
  assert val == 0.1
209
244
 
245
+ # test error handling
246
+ with pytest.raises(RuntimeError):
247
+ vocs.select_best(pd.DataFrame())
248
+
249
+ vocs.constraints = {"c1": ["GREATER_THAN", 10.5]}
250
+ with pytest.raises(RuntimeError):
251
+ vocs.select_best(pd.DataFrame())
252
+
210
253
  @pytest.mark.filterwarnings("ignore: All-NaN axis encountered")
211
254
  def test_cumulative_optimum(self):
212
255
  vocs = deepcopy(TEST_VOCS_BASE)
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-02-08T15:50:49-0800",
11
+ "date": "2024-05-07T12:53:58-0400",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "27fc0ce1d9c798f72d11e08f673bb7d468ac81b2",
15
- "version": "2.2.1"
14
+ "full-revisionid": "2a01895f05a19d2a26b527c4c47baaf099cd0110",
15
+ "version": "2.2.3"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -31,7 +31,8 @@ class AsynchronousXopt(Xopt):
31
31
  """
32
32
  Submit data to evaluator and return futures indexed to internal futures list.
33
33
 
34
- Args:
34
+ Parameters
35
+ ----------
35
36
  input_data: dataframe containing input data
36
37
 
37
38
  """
@@ -405,7 +405,12 @@ class Xopt(XoptBaseModel):
405
405
  else:
406
406
  return new_data
407
407
 
408
- def random_evaluate(self, n_samples=1, seed=None, **kwargs):
408
+ def random_evaluate(
409
+ self,
410
+ n_samples=None,
411
+ seed=None,
412
+ custom_bounds: dict = None,
413
+ ):
409
414
  """
410
415
  Convenience method to generate random inputs using VOCs and evaluate them.
411
416
 
@@ -419,8 +424,9 @@ class Xopt(XoptBaseModel):
419
424
  The number of random samples to generate.
420
425
  seed : int, optional
421
426
  The random seed for reproducibility.
422
- **kwargs
423
- Additional keyword arguments for generating random inputs.
427
+ custom_bounds : dict, optional
428
+ Dictionary of vocs-like ranges for random sampling
429
+
424
430
 
425
431
  Returns
426
432
  -------
@@ -428,7 +434,9 @@ class Xopt(XoptBaseModel):
428
434
  The results of the evaluations added to the internal DataFrame.
429
435
 
430
436
  """
431
- random_inputs = self.vocs.random_inputs(n_samples, seed=seed, **kwargs)
437
+ random_inputs = self.vocs.random_inputs(
438
+ n_samples, seed=seed, custom_bounds=custom_bounds, include_constants=True
439
+ )
432
440
  result = self.evaluate_data(random_inputs)
433
441
  return result
434
442
 
@@ -37,7 +37,9 @@ class Evaluator(XoptBaseModel):
37
37
  executor : NormalExecutor
38
38
  NormalExecutor or any instantiated Executor object
39
39
  vectorized : bool, default=False
40
- If true,
40
+ If true, lists of evaluation points will be sent to the evaluator
41
+ function to be processed in parallel instead of evaluated seperately via
42
+ mapping.
41
43
  """
42
44
 
43
45
  function: Callable
@@ -20,7 +20,8 @@ class ModelListExpectedInformationGain(MultiObjectiveAnalyticAcquisitionFunction
20
20
  >>> EIG = ExpectedInformationGain(model, algo)
21
21
  >>> eig = EIG(test_X)
22
22
 
23
- Args:
23
+ Parameters
24
+ ----------
24
25
  model: A fitted independent multi-output (ModelList) model.
25
26
  """
26
27
 
@@ -62,13 +63,15 @@ class ModelListExpectedInformationGain(MultiObjectiveAnalyticAcquisitionFunction
62
63
  def forward(self, X: Tensor) -> Tensor:
63
64
  r"""Evaluate Expected Information Gain on the candidate set X.
64
65
 
65
- Args:
66
+ Parameters
67
+ ----------
66
68
  X: A `(b1 x ... bk) x 1 x d`-dim batched tensor of `d`-dim design points.
67
69
  Expected Information Gain is computed for each point individually,
68
70
  i.e., what is considered are the marginal posteriors, not the
69
71
  joint.
70
72
 
71
- Returns:
73
+ Returns
74
+ -------
72
75
  A `(b1 x ... bk)`-dim tensor of Expected Information Gain values at the
73
76
  given design points `X`.
74
77
  """
@@ -46,7 +46,7 @@ class GridScanAlgorithm(Algorithm, ABC):
46
46
 
47
47
 
48
48
  class GridMinimize(GridScanAlgorithm):
49
- model_names_ordered: List[str] = Field(
49
+ observable_names_ordered: List[str] = Field(
50
50
  default=["y1"],
51
51
  description="names of observable/objective models used in this algorithm",
52
52
  )
@@ -3,7 +3,8 @@ import pickle
3
3
  from copy import deepcopy
4
4
  from typing import Dict
5
5
 
6
- from pydantic import Field
6
+ from pydantic import Field, field_validator
7
+ from pydantic_core.core_schema import ValidationInfo
7
8
 
8
9
  from xopt.generators.bayesian.bax.acquisition import ModelListExpectedInformationGain
9
10
  from xopt.generators.bayesian.bax.algorithms import Algorithm
@@ -24,6 +25,12 @@ class BaxGenerator(BayesianGenerator):
24
25
 
25
26
  _n_calls: int = 0
26
27
 
28
+ @field_validator("vocs", mode="after")
29
+ def validate_vocs(cls, v, info: ValidationInfo):
30
+ if v.n_objectives != 0:
31
+ raise ValueError("this generator only supports observables")
32
+ return v
33
+
27
34
  def generate(self, n_candidates: int) -> list[dict]:
28
35
  self._n_calls += 1
29
36
  return super().generate(n_candidates)
@@ -31,7 +38,7 @@ class BaxGenerator(BayesianGenerator):
31
38
  def _get_acquisition(self, model):
32
39
  bax_model_ids = [
33
40
  self.vocs.output_names.index(name)
34
- for name in self.algorithm.model_names_ordered
41
+ for name in self.algorithm.observable_names_ordered
35
42
  ]
36
43
  bax_model = model.subset_output(bax_model_ids)
37
44
  eig = ModelListExpectedInformationGain(
@@ -13,7 +13,6 @@ from xopt.generators.bayesian.bayesian_generator import (
13
13
  BayesianGenerator,
14
14
  formatted_base_docstring,
15
15
  )
16
- from xopt.generators.bayesian.objectives import create_exploration_objective
17
16
 
18
17
 
19
18
  class BayesianExplorationGenerator(BayesianGenerator):
@@ -25,7 +24,7 @@ class BayesianExplorationGenerator(BayesianGenerator):
25
24
  @field_validator("vocs", mode="after")
26
25
  def validate_vocs(cls, v, info: ValidationInfo):
27
26
  if v.n_objectives != 0:
28
- raise ValueError("this generator only supports zero objectives only")
27
+ raise ValueError("this generator only supports observables")
29
28
  return v
30
29
 
31
30
  def _get_acquisition(self, model):
@@ -38,12 +37,6 @@ class BayesianExplorationGenerator(BayesianGenerator):
38
37
 
39
38
  return qPV
40
39
 
41
- def _get_objective(self):
42
- """return exploration objective, which only captures the output of the first
43
- model output"""
44
-
45
- return create_exploration_objective(self.vocs, self._tkwargs)
46
-
47
40
 
48
41
  class qPosteriorVariance(MCAcquisitionFunction):
49
42
  def __init__(
@@ -55,7 +48,8 @@ class qPosteriorVariance(MCAcquisitionFunction):
55
48
  X_pending: Optional[Tensor] = None,
56
49
  ) -> None:
57
50
  r"""q-Upper Confidence Bound.
58
- Args:
51
+ Parameters
52
+ ----------
59
53
  model: A fitted model.
60
54
  sampler: The sampler used to draw base samples. Defaults to
61
55
  `SobolQMCNormalSampler(num_samples=512, collapse_batch_dims=True)`
@@ -79,10 +73,13 @@ class qPosteriorVariance(MCAcquisitionFunction):
79
73
  @t_batch_mode_transform()
80
74
  def forward(self, X: Tensor) -> Tensor:
81
75
  r"""Evaluate qUpperConfidenceBound on the candidate set `X`.
82
- Args:
76
+ Parameters
77
+ ----------
83
78
  X: A `batch_sahpe x q x d`-dim Tensor of t-batches with `q` `d`-dim design
84
79
  points each.
85
- Returns:
80
+
81
+ Returns
82
+ -------
86
83
  A `batch_shape'`-dim Tensor of Upper Confidence Bound values at the given
87
84
  design points `X`, where `batch_shape'` is the broadcasted batch shape of
88
85
  model and input `X`.
@@ -375,6 +375,11 @@ class BayesianGenerator(Generator, ABC):
375
375
  # get bounds for each fixed_feature (vocs bounds take precedent)
376
376
  for key in self.fixed_features:
377
377
  if key not in variable_bounds:
378
+ if key not in data:
379
+ raise KeyError(
380
+ "generator data needs to contain fixed feature "
381
+ f"column name `{key}`"
382
+ )
378
383
  f_data = data[key]
379
384
  bounds = [f_data.min(), f_data.max()]
380
385
  if bounds[1] - bounds[0] < 1e-8:
@@ -545,11 +550,20 @@ class BayesianGenerator(Generator, ABC):
545
550
  pass
546
551
 
547
552
  def _get_objective(self):
548
- """return default objective (scalar objective) determined by vocs"""
553
+ """
554
+ return default objective (scalar objective) determined by vocs
555
+
556
+ If objectives are specified the returned function will weight model
557
+ by +/- 1.0 according to MAXIMIZE/MINIMIZE keys in vocs.
558
+
559
+ If no objectives are specified, the returned function will weight observable
560
+ models by +1.0. This is used in Bayesian exploration.
561
+
562
+ """
549
563
  return create_mc_objective(self.vocs, self._tkwargs)
550
564
 
551
565
  def _get_constraint_callables(self):
552
- """return default objective (scalar objective) determined by vocs"""
566
+ """return constratint callable determined by vocs"""
553
567
  constraint_callables = create_constraint_callables(self.vocs)
554
568
  if len(constraint_callables) == 0:
555
569
  constraint_callables = None
@@ -755,7 +769,7 @@ class MultiObjectiveBayesianGenerator(BayesianGenerator, ABC):
755
769
 
756
770
  n_objectives = self.vocs.n_objectives
757
771
  weights = torch.zeros(n_objectives)
758
- weights = set_botorch_weights(weights, self.vocs)
772
+ weights = set_botorch_weights(self.vocs).to(**self._tkwargs)
759
773
  objective_data = objective_data * weights
760
774
 
761
775
  # compute hypervolume
@@ -18,8 +18,8 @@ class FeasibilityObjective(GenericMCObjective):
18
18
  infeasible_cost: float = 0.0,
19
19
  eta: float = 1e-3,
20
20
  ) -> None:
21
- def ones_callable(X):
22
- return torch.ones(X.shape[:-1])
21
+ def ones_callable(Z, X=None):
22
+ return torch.ones(Z.shape[:-1])
23
23
 
24
24
  super().__init__(objective=ones_callable)
25
25
  self.constraints = constraints
@@ -29,7 +29,8 @@ class FeasibilityObjective(GenericMCObjective):
29
29
  def forward(self, samples: Tensor, X: Optional[Tensor] = None) -> Tensor:
30
30
  r"""Evaluate the feasibility-weighted objective on the samples.
31
31
 
32
- Args:
32
+ Parameters
33
+ ----------
33
34
  samples: A `sample_shape x batch_shape x q x m`-dim Tensors of
34
35
  samples from a model posterior.
35
36
  X: A `batch_shape x q x d`-dim tensor of inputs. Relevant only if
@@ -38,7 +38,8 @@ class XoptHeteroskedasticSingleTaskGP(BatchedMultiOutputGPyTorchModel, ExactGP):
38
38
  to specify mean and covariance modules.
39
39
 
40
40
 
41
- Args:
41
+ Parameters
42
+ ----------
42
43
  train_X: A `batch_shape x n x d` tensor of training features.
43
44
  train_Y: A `batch_shape x n x m` tensor of training observations.
44
45
  train_Yvar: A `batch_shape x n x m` tensor of observed measurement