xopt 2.4.2__tar.gz → 2.4.4__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 (148) hide show
  1. {xopt-2.4.2 → xopt-2.4.4}/PKG-INFO +1 -1
  2. {xopt-2.4.2 → xopt-2.4.4}/xopt/_version.py +2 -2
  3. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bax/visualize.py +1 -1
  4. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bayesian_generator.py +63 -26
  5. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/expected_improvement.py +3 -5
  6. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/mggpo.py +1 -1
  7. xopt-2.4.4/xopt/generators/bayesian/mobo.py +135 -0
  8. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/multi_fidelity.py +1 -1
  9. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/time_dependent.py +2 -2
  10. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/turbo.py +32 -13
  11. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/utils.py +7 -3
  12. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/ga/cnsga.py +4 -2
  13. {xopt-2.4.2 → xopt-2.4.4}/xopt/numerical_optimizer.py +5 -2
  14. xopt-2.4.4/xopt/resources/test_functions/zdt.py +51 -0
  15. xopt-2.4.4/xopt/tests/generators/bayesian/test_mobo.py +249 -0
  16. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_turbo.py +36 -3
  17. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_numerical_optimizer.py +4 -1
  18. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_vocs.py +34 -0
  19. {xopt-2.4.2 → xopt-2.4.4}/xopt/vocs.py +16 -2
  20. {xopt-2.4.2 → xopt-2.4.4}/xopt.egg-info/PKG-INFO +1 -1
  21. {xopt-2.4.2 → xopt-2.4.4}/xopt.egg-info/SOURCES.txt +1 -0
  22. xopt-2.4.2/xopt/generators/bayesian/mobo.py +0 -66
  23. xopt-2.4.2/xopt/tests/generators/bayesian/test_mobo.py +0 -96
  24. {xopt-2.4.2 → xopt-2.4.4}/.flake8 +0 -0
  25. {xopt-2.4.2 → xopt-2.4.4}/.pre-commit-config.yaml +0 -0
  26. {xopt-2.4.2 → xopt-2.4.4}/LICENSE +0 -0
  27. {xopt-2.4.2 → xopt-2.4.4}/MANIFEST.in +0 -0
  28. {xopt-2.4.2 → xopt-2.4.4}/README.md +0 -0
  29. {xopt-2.4.2 → xopt-2.4.4}/benchmarks/__init__.py +0 -0
  30. {xopt-2.4.2 → xopt-2.4.4}/benchmarks/test_mo.py +0 -0
  31. {xopt-2.4.2 → xopt-2.4.4}/docs/algorithms.md +0 -0
  32. {xopt-2.4.2 → xopt-2.4.4}/docs/api/evaluator.md +0 -0
  33. {xopt-2.4.2 → xopt-2.4.4}/docs/api/generators/bayesian.md +0 -0
  34. {xopt-2.4.2 → xopt-2.4.4}/docs/api/generators/genetic.md +0 -0
  35. {xopt-2.4.2 → xopt-2.4.4}/docs/api/generators/scipy.md +0 -0
  36. {xopt-2.4.2 → xopt-2.4.4}/docs/api/generators.md +0 -0
  37. {xopt-2.4.2 → xopt-2.4.4}/docs/api/vocs.md +0 -0
  38. {xopt-2.4.2 → xopt-2.4.4}/docs/api/xopt.md +0 -0
  39. {xopt-2.4.2 → xopt-2.4.4}/docs/assets/Xopt-logo.png +0 -0
  40. {xopt-2.4.2 → xopt-2.4.4}/docs/assets/xopt_overview.pdf +0 -0
  41. {xopt-2.4.2 → xopt-2.4.4}/docs/index.md +0 -0
  42. {xopt-2.4.2 → xopt-2.4.4}/docs/installation.md +0 -0
  43. {xopt-2.4.2 → xopt-2.4.4}/docs/overrides/main.html +0 -0
  44. {xopt-2.4.2 → xopt-2.4.4}/docs/stylesheets/extra.css +0 -0
  45. {xopt-2.4.2 → xopt-2.4.4}/environment.yml +0 -0
  46. {xopt-2.4.2 → xopt-2.4.4}/mkdocs.yml +0 -0
  47. {xopt-2.4.2 → xopt-2.4.4}/pyproject.toml +0 -0
  48. {xopt-2.4.2 → xopt-2.4.4}/scripts/execute_notebooks.bash +0 -0
  49. {xopt-2.4.2 → xopt-2.4.4}/scripts/run_benchmarks.py +0 -0
  50. {xopt-2.4.2 → xopt-2.4.4}/scripts/run_examples.py +0 -0
  51. {xopt-2.4.2 → xopt-2.4.4}/scripts/run_tests.py +0 -0
  52. {xopt-2.4.2 → xopt-2.4.4}/setup.cfg +0 -0
  53. {xopt-2.4.2 → xopt-2.4.4}/xopt/__init__.py +0 -0
  54. {xopt-2.4.2 → xopt-2.4.4}/xopt/asynchronous.py +0 -0
  55. {xopt-2.4.2 → xopt-2.4.4}/xopt/base.py +0 -0
  56. {xopt-2.4.2 → xopt-2.4.4}/xopt/errors.py +0 -0
  57. {xopt-2.4.2 → xopt-2.4.4}/xopt/evaluator.py +0 -0
  58. {xopt-2.4.2 → xopt-2.4.4}/xopt/generator.py +0 -0
  59. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/__init__.py +0 -0
  60. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/__init__.py +0 -0
  61. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/base_model.py +0 -0
  62. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bax/__init__.py +0 -0
  63. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bax/acquisition.py +0 -0
  64. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bax/algorithms.py +0 -0
  65. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bax_generator.py +0 -0
  66. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/bayesian_exploration.py +0 -0
  67. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/__init__.py +0 -0
  68. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/constrained_acquisition.py +0 -0
  69. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/hessian_kernel.py +0 -0
  70. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/heteroskedastic.py +0 -0
  71. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/identity.py +0 -0
  72. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/log_acquisition_function.py +0 -0
  73. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/multi_fidelity.py +0 -0
  74. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/custom_botorch/proximal.py +0 -0
  75. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/models/__init__.py +0 -0
  76. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/models/prior_mean.py +0 -0
  77. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/models/standard.py +0 -0
  78. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/models/time_dependent.py +0 -0
  79. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/objectives.py +0 -0
  80. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/upper_confidence_bound.py +0 -0
  81. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/bayesian/visualize.py +0 -0
  82. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/es/__init__.py +0 -0
  83. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/es/extremumseeking.py +0 -0
  84. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/ga/__init__.py +0 -0
  85. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/ga/deap_creator.py +0 -0
  86. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/ga/deap_fitness_with_constraints.py +0 -0
  87. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/ga/fitness_with_constraints.py +0 -0
  88. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/random.py +0 -0
  89. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/rcds/__init__.py +0 -0
  90. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/rcds/rcds.py +0 -0
  91. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/scipy/__init__.py +0 -0
  92. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/scipy/latin_hypercube.py +0 -0
  93. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/scipy/neldermead.py +0 -0
  94. {xopt-2.4.2 → xopt-2.4.4}/xopt/generators/utils.py +0 -0
  95. {xopt-2.4.2 → xopt-2.4.4}/xopt/log.py +0 -0
  96. {xopt-2.4.2 → xopt-2.4.4}/xopt/mpi/__init__.py +0 -0
  97. {xopt-2.4.2 → xopt-2.4.4}/xopt/mpi/run.py +0 -0
  98. {xopt-2.4.2 → xopt-2.4.4}/xopt/pydantic.py +0 -0
  99. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/__init__.py +0 -0
  100. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/benchmarking.py +0 -0
  101. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/__init__.py +0 -0
  102. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/ackley_20.py +0 -0
  103. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/haverly_pooling.py +0 -0
  104. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/modified_tnk.py +0 -0
  105. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/multi_objective.py +0 -0
  106. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/problem.py +0 -0
  107. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/rosenbrock.py +0 -0
  108. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/sinusoid_1d.py +0 -0
  109. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/sphere_100.py +0 -0
  110. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/sphere_20.py +0 -0
  111. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/sphere_50.py +0 -0
  112. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/test_functions/tnk.py +0 -0
  113. {xopt-2.4.2 → xopt-2.4.4}/xopt/resources/testing.py +0 -0
  114. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/__init__.py +0 -0
  115. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/__init__.py +0 -0
  116. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/__init__.py +0 -0
  117. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_bax.py +0 -0
  118. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_bayesian_exploration.py +0 -0
  119. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_bayesian_generator.py +0 -0
  120. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_constraints.py +0 -0
  121. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_custom_model.py +0 -0
  122. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_expected_improvement.py +0 -0
  123. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_hessian_kernel.py +0 -0
  124. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_high_level.py +0 -0
  125. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_mggpo.py +0 -0
  126. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_model_constructor.py +0 -0
  127. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_multi_fidelity.py +0 -0
  128. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_time_dependent_bo.py +0 -0
  129. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_upper_confidence_bound.py +0 -0
  130. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/bayesian/test_utils.py +0 -0
  131. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/ga/test_cnsga.py +0 -0
  132. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/test_extremum_seeking.py +0 -0
  133. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/test_latin_hypercube.py +0 -0
  134. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/test_neldermead.py +0 -0
  135. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/test_random.py +0 -0
  136. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/generators/test_rcds.py +0 -0
  137. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_evaluator.py +0 -0
  138. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_generator.py +0 -0
  139. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_io.py +0 -0
  140. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_mpi.py +0 -0
  141. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_perf.py +0 -0
  142. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_pydantic.py +0 -0
  143. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_utils.py +0 -0
  144. {xopt-2.4.2 → xopt-2.4.4}/xopt/tests/test_xopt.py +0 -0
  145. {xopt-2.4.2 → xopt-2.4.4}/xopt/utils.py +0 -0
  146. {xopt-2.4.2 → xopt-2.4.4}/xopt.egg-info/dependency_links.txt +0 -0
  147. {xopt-2.4.2 → xopt-2.4.4}/xopt.egg-info/requires.txt +0 -0
  148. {xopt-2.4.2 → xopt-2.4.4}/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.4.2
3
+ Version: 2.4.4
4
4
  Summary: Flexible optimization of arbitrary problems in Python.
5
5
  Author: Christopher Mayes, Ryan Roussel
6
6
  License: Apache License
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.4.2'
16
- __version_tuple__ = version_tuple = (2, 4, 2)
15
+ __version__ = version = '2.4.4'
16
+ __version_tuple__ = version_tuple = (2, 4, 4)
@@ -103,7 +103,7 @@ def visualize_virtual_objective(
103
103
  bounds = generator._get_optimization_bounds()
104
104
  kwargs = kwargs if kwargs else {}
105
105
  objective_values = generator.algorithm.evaluate_virtual_objective(
106
- bax_model, x, bounds, tkwargs=generator._tkwargs, n_samples=n_samples, **kwargs
106
+ bax_model, x, bounds, tkwargs=generator.tkwargs, n_samples=n_samples, **kwargs
107
107
  )
108
108
 
109
109
  # get sample stats
@@ -4,13 +4,14 @@ import time
4
4
  import warnings
5
5
  from abc import ABC, abstractmethod
6
6
  from copy import deepcopy
7
- from typing import Dict, List, Optional
7
+ from typing import Dict, List, Optional, Union
8
8
 
9
9
  import pandas as pd
10
10
  import torch
11
11
  from botorch.acquisition import FixedFeatureAcquisitionFunction, qUpperConfidenceBound
12
12
  from botorch.models.model import Model
13
13
  from botorch.sampling import get_sampler
14
+ from botorch.utils.multi_objective import is_non_dominated
14
15
  from botorch.utils.multi_objective.box_decompositions import DominatedPartitioning
15
16
  from gpytorch import Module
16
17
  from pydantic import Field, field_validator, PositiveInt, SerializeAsAny
@@ -384,7 +385,7 @@ class BayesianGenerator(Generator, ABC):
384
385
  self.vocs.output_names,
385
386
  data,
386
387
  {name: variable_bounds[name] for name in self.model_input_names},
387
- **self._tkwargs,
388
+ **self.tkwargs,
388
389
  )
389
390
 
390
391
  if update_internal:
@@ -407,8 +408,18 @@ class BayesianGenerator(Generator, ABC):
407
408
  # get acquisition function
408
409
  acq_funct = self.get_acquisition(model)
409
410
 
410
- # get candidates
411
- candidates = self.numerical_optimizer.optimize(acq_funct, bounds, n_candidates)
411
+ # get initial candidates to start acquisition function optimization
412
+ initial_points = self._get_initial_conditions(n_candidates)
413
+
414
+ # get candidates -- grid optimizer does not support batch_initial_conditions
415
+ if isinstance(self.numerical_optimizer, GridOptimizer):
416
+ candidates = self.numerical_optimizer.optimize(
417
+ acq_funct, bounds, n_candidates
418
+ )
419
+ else:
420
+ candidates = self.numerical_optimizer.optimize(
421
+ acq_funct, bounds, n_candidates, batch_initial_conditions=initial_points
422
+ )
412
423
  return candidates
413
424
 
414
425
  def get_training_data(self, data: pd.DataFrame) -> pd.DataFrame:
@@ -456,7 +467,7 @@ class BayesianGenerator(Generator, ABC):
456
467
  input names (variables), and the resulting tensor is configured with the data
457
468
  type and device settings from the generator.
458
469
  """
459
- return torch.tensor(data[self.model_input_names].to_numpy(), **self._tkwargs)
470
+ return torch.tensor(data[self.model_input_names].to_numpy(), **self.tkwargs)
460
471
 
461
472
  def get_acquisition(self, model):
462
473
  """
@@ -536,6 +547,11 @@ class BayesianGenerator(Generator, ABC):
536
547
  """displays the GP models"""
537
548
  return visualize_generator_model(self, **kwargs)
538
549
 
550
+ def _get_initial_conditions(self, n_candidates=1) -> Union[Tensor, None]:
551
+ """overwrite if algorithm should specifiy initial candidates for optimizing
552
+ the acquisition function"""
553
+ return None
554
+
539
555
  def _process_candidates(self, candidates: Tensor):
540
556
  """process pytorch candidates from optimizing the acquisition function"""
541
557
  logger.debug(f"Best candidate from optimize {candidates}")
@@ -581,7 +597,7 @@ class BayesianGenerator(Generator, ABC):
581
597
 
582
598
  return self.custom_objective
583
599
  else:
584
- return create_mc_objective(self.vocs, self._tkwargs)
600
+ return create_mc_objective(self.vocs, self.tkwargs)
585
601
 
586
602
  def _get_constraint_callables(self):
587
603
  """return constratint callable determined by vocs"""
@@ -591,7 +607,7 @@ class BayesianGenerator(Generator, ABC):
591
607
  return constraint_callables
592
608
 
593
609
  @property
594
- def _tkwargs(self):
610
+ def tkwargs(self):
595
611
  # set device and data type for generator
596
612
  device = "cpu"
597
613
  if self.use_cuda:
@@ -627,7 +643,7 @@ class BayesianGenerator(Generator, ABC):
627
643
 
628
644
  def _get_bounds(self):
629
645
  """convert bounds from vocs to torch tensors"""
630
- return torch.tensor(self.vocs.bounds, **self._tkwargs)
646
+ return torch.tensor(self.vocs.bounds, **self.tkwargs)
631
647
 
632
648
  def _get_optimization_bounds(self):
633
649
  """
@@ -720,7 +736,7 @@ class BayesianGenerator(Generator, ABC):
720
736
  "from, add data first to use during BO"
721
737
  )
722
738
  last_point = torch.tensor(
723
- self.data[self.vocs.variable_names].iloc[-1].to_numpy(), **self._tkwargs
739
+ self.data[self.vocs.variable_names].iloc[-1].to_numpy(), **self.tkwargs
724
740
  )
725
741
 
726
742
  # bound lengths based on vocs for normalization
@@ -728,8 +744,8 @@ class BayesianGenerator(Generator, ABC):
728
744
 
729
745
  # get maximum travel distances
730
746
  max_travel_distances = torch.tensor(
731
- self.max_travel_distances, **self._tkwargs
732
- ) * torch.tensor(lengths, **self._tkwargs)
747
+ self.max_travel_distances, **self.tkwargs
748
+ ) * torch.tensor(lengths, **self.tkwargs)
733
749
  max_travel_bounds = torch.stack(
734
750
  (last_point - max_travel_distances, last_point + max_travel_distances)
735
751
  )
@@ -774,33 +790,54 @@ class MultiObjectiveBayesianGenerator(BayesianGenerator, ABC):
774
790
  supported"
775
791
  )
776
792
 
777
- return torch.tensor(pt, **self._tkwargs)
793
+ return torch.tensor(pt, **self.tkwargs)
778
794
 
779
- def calculate_hypervolume(self):
780
- """compute hypervolume given data"""
781
- objective_data = torch.tensor(
782
- self.vocs.objective_data(self.data, return_raw=True).to_numpy()
795
+ def _get_scaled_data(self):
796
+ """get scaled input/objective data for use with botorch logic which assumes
797
+ maximization for each objective"""
798
+ var_df, obj_df, _, _ = self.vocs.extract_data(
799
+ self.data, return_valid=True, return_raw=True
783
800
  )
784
801
 
785
- # hypervolume must only take into account feasible data points
786
- if self.vocs.n_constraints > 0:
787
- objective_data = objective_data[
788
- self.vocs.feasibility_data(self.data)["feasible"].to_list()
789
- ]
802
+ variable_data = torch.tensor(var_df[self.vocs.variable_names].to_numpy())
803
+ objective_data = torch.tensor(obj_df[self.vocs.objective_names].to_numpy())
804
+ weights = set_botorch_weights(self.vocs).to(**self.tkwargs)[
805
+ : self.vocs.n_objectives
806
+ ]
807
+ return variable_data, objective_data * weights, weights
790
808
 
791
- n_objectives = self.vocs.n_objectives
792
- weights = torch.zeros(n_objectives)
793
- weights = set_botorch_weights(self.vocs).to(**self._tkwargs)
794
- objective_data = objective_data * weights
809
+ def calculate_hypervolume(self):
810
+ """compute hypervolume given data"""
795
811
 
796
812
  # compute hypervolume
797
813
  bd = DominatedPartitioning(
798
- ref_point=self.torch_reference_point, Y=objective_data
814
+ ref_point=self.torch_reference_point, Y=self._get_scaled_data()[1]
799
815
  )
800
816
  volume = bd.compute_hypervolume().item()
801
817
 
802
818
  return volume
803
819
 
820
+ def get_pareto_front(self):
821
+ """compute the pareto front x/y values given data"""
822
+ variable_data, objective_data, weights = self._get_scaled_data()
823
+ obj_data = torch.vstack(
824
+ (self.torch_reference_point.unsqueeze(0), objective_data)
825
+ )
826
+ var_data = torch.vstack(
827
+ (
828
+ torch.full_like(variable_data[0], float("Nan")).unsqueeze(0),
829
+ variable_data,
830
+ )
831
+ )
832
+ non_dominated = is_non_dominated(obj_data)
833
+
834
+ # note need to undo weights for real number output
835
+ # only return values if non nan values exist
836
+ if torch.all(torch.isnan(var_data[non_dominated])):
837
+ return None, None
838
+ else:
839
+ return var_data[non_dominated], obj_data[non_dominated] / weights
840
+
804
841
 
805
842
  def formatted_base_docstring():
806
843
  return "\nBase Generator\n---------------\n" + BayesianGenerator.__doc__
@@ -40,7 +40,7 @@ class ExpectedImprovementGenerator(BayesianGenerator):
40
40
  # analytic acquisition function for single candidate generation with
41
41
  # basic objective
42
42
  # note that the analytic version cannot handle custom objectives
43
- weights = set_botorch_weights(self.vocs).to(**self._tkwargs)
43
+ weights = set_botorch_weights(self.vocs).to(**self.tkwargs)
44
44
  posterior_transform = ScalarizedPosteriorTransform(weights)
45
45
  acq = ExpectedImprovement(
46
46
  model, best_f=best_f, posterior_transform=posterior_transform
@@ -52,14 +52,12 @@ class ExpectedImprovementGenerator(BayesianGenerator):
52
52
  """get best function value for EI based on the objective"""
53
53
  if isinstance(objective, CustomXoptObjective):
54
54
  best_f = objective(
55
- torch.tensor(
56
- self.vocs.observable_data(data).to_numpy(), **self._tkwargs
57
- )
55
+ torch.tensor(self.vocs.observable_data(data).to_numpy(), **self.tkwargs)
58
56
  ).max()
59
57
  else:
60
58
  # analytic acquisition function for single candidate generation
61
59
  best_f = -torch.tensor(
62
- self.vocs.objective_data(data).min().values, **self._tkwargs
60
+ self.vocs.objective_data(data).min().values, **self.tkwargs
63
61
  )
64
62
 
65
63
  return best_f
@@ -32,7 +32,7 @@ class MGGPOGenerator(MultiObjectiveBayesianGenerator):
32
32
  ga_candidates = self.ga_generator.generate(n_candidates * 10)
33
33
  ga_candidates = pd.DataFrame(ga_candidates)[self.vocs.variable_names].to_numpy()
34
34
  ga_candidates = torch.unique(
35
- torch.tensor(ga_candidates, **self._tkwargs), dim=0
35
+ torch.tensor(ga_candidates, **self.tkwargs), dim=0
36
36
  ).reshape(-1, 1, self.vocs.n_variables)
37
37
 
38
38
  if ga_candidates.shape[0] < n_candidates:
@@ -0,0 +1,135 @@
1
+ from typing import Union
2
+
3
+ import torch
4
+ from botorch.acquisition import FixedFeatureAcquisitionFunction
5
+ from botorch.acquisition.multi_objective import qNoisyExpectedHypervolumeImprovement
6
+ from botorch.acquisition.multi_objective.logei import (
7
+ qLogNoisyExpectedHypervolumeImprovement,
8
+ )
9
+ from botorch.utils import draw_sobol_samples
10
+ from pydantic import Field
11
+ from torch import Tensor
12
+
13
+ from xopt.generators.bayesian.bayesian_generator import MultiObjectiveBayesianGenerator
14
+
15
+ from xopt.generators.bayesian.objectives import create_mobo_objective
16
+ from xopt.numerical_optimizer import LBFGSOptimizer
17
+
18
+
19
+ class MOBOGenerator(MultiObjectiveBayesianGenerator):
20
+ name = "mobo"
21
+ supports_batch_generation: bool = True
22
+ use_pf_as_initial_points: bool = Field(
23
+ False,
24
+ description="flag to specify if pareto front points are to be used during "
25
+ "optimization of the acquisition function",
26
+ )
27
+ __doc__ = """Implements Multi-Objective Bayesian Optimization using the Expected
28
+ Hypervolume Improvement acquisition function"""
29
+
30
+ def _get_objective(self):
31
+ return create_mobo_objective(self.vocs)
32
+
33
+ def get_acquisition(self, model):
34
+ """
35
+ Returns a function that can be used to evaluate the acquisition function
36
+ """
37
+ if model is None:
38
+ raise ValueError("model cannot be None")
39
+
40
+ # get base acquisition function
41
+ acq = self._get_acquisition(model)
42
+
43
+ # apply fixed features if specified in the generator
44
+ if self.fixed_features is not None:
45
+ # get input dim
46
+ dim = len(self.model_input_names)
47
+ columns = []
48
+ values = []
49
+ for name, value in self.fixed_features.items():
50
+ columns += [self.model_input_names.index(name)]
51
+ values += [value]
52
+
53
+ acq = FixedFeatureAcquisitionFunction(
54
+ acq_function=acq, d=dim, columns=columns, values=values
55
+ )
56
+
57
+ return acq
58
+
59
+ def _get_acquisition(self, model):
60
+ inputs = self.get_input_data(self.data)
61
+ sampler = self._get_sampler(model)
62
+
63
+ if self.log_transform_acquisition_function:
64
+ acqclass = qLogNoisyExpectedHypervolumeImprovement
65
+ else:
66
+ acqclass = qNoisyExpectedHypervolumeImprovement
67
+
68
+ acq = acqclass(
69
+ model,
70
+ X_baseline=inputs,
71
+ constraints=self._get_constraint_callables(),
72
+ ref_point=self.torch_reference_point,
73
+ sampler=sampler,
74
+ objective=self._get_objective(),
75
+ cache_root=False,
76
+ prune_baseline=True,
77
+ )
78
+
79
+ return acq
80
+
81
+ def _get_initial_conditions(self, n_candidates=1) -> Union[Tensor, None]:
82
+ """
83
+ generate initial candidates for optimizing the acquisition function based on
84
+ the pareto front
85
+
86
+ If `use_pf_as_initial_points` flag is set to true then the current
87
+ Pareto-optimal set is used as initial points for optimizing the acquisition
88
+ function instead of randomly selected points (random points fill in the set
89
+ if `num_restarts` is greater than the number of points in the Pareto set).
90
+
91
+ Returns:
92
+ A `num_restarts x q x d` tensor of initial conditions.
93
+
94
+ """
95
+ if self.use_pf_as_initial_points:
96
+ if isinstance(self.numerical_optimizer, LBFGSOptimizer):
97
+ bounds = self._get_optimization_bounds()
98
+ num_restarts = self.numerical_optimizer.n_restarts
99
+
100
+ pf_locations, _ = self.get_pareto_front()
101
+
102
+ # if there is no pareto front just return None to revert back to
103
+ # default behavior
104
+ if pf_locations is None:
105
+ return None
106
+
107
+ initial_points = torch.clone(pf_locations)
108
+
109
+ # add the q dimension
110
+ initial_points = initial_points.unsqueeze(1)
111
+ initial_points = initial_points.expand([-1, n_candidates, -1])
112
+
113
+ # initial_points must equal the number of restarts
114
+ if len(initial_points) < num_restarts:
115
+ # add random points to the list inside the bounds
116
+ sobol_samples = draw_sobol_samples(
117
+ bounds, num_restarts - len(initial_points), n_candidates
118
+ )
119
+
120
+ initial_points = torch.cat([initial_points, sobol_samples])
121
+ elif len(initial_points) > num_restarts:
122
+ # if there are too many select the first `num_restarts` points at
123
+ # random
124
+ initial_points = initial_points[
125
+ torch.randperm(len(initial_points))
126
+ ][:num_restarts]
127
+
128
+ return initial_points
129
+ else:
130
+ raise RuntimeWarning(
131
+ "cannot use PF as initial optimization points "
132
+ "for non-LBFGS optimizers, ignoring flag"
133
+ )
134
+
135
+ return None
@@ -155,7 +155,7 @@ class MultiFidelityGenerator(MOBOGenerator):
155
155
  """select the best point at the maximum fidelity"""
156
156
 
157
157
  # define single objective based on vocs
158
- weights = torch.zeros(self.vocs.n_outputs, **self._tkwargs)
158
+ weights = torch.zeros(self.vocs.n_outputs, **self.tkwargs)
159
159
  for idx, ele in enumerate(self.vocs.objective_names):
160
160
  if self.vocs.objectives[ele] == "MINIMIZE":
161
161
  weights[idx] = -1.0
@@ -95,7 +95,7 @@ class TimeDependentBayesianGenerator(BayesianGenerator, ABC):
95
95
  type and device settings from the generator.
96
96
  """
97
97
  return torch.tensor(
98
- data[self.model_input_names + ["time"]].to_numpy(), **self._tkwargs
98
+ data[self.model_input_names + ["time"]].to_numpy(), **self.tkwargs
99
99
  )
100
100
 
101
101
  def get_acquisition(self, model):
@@ -103,7 +103,7 @@ class TimeDependentBayesianGenerator(BayesianGenerator, ABC):
103
103
 
104
104
  # identify which column has the `time` attribute
105
105
  column = [-1]
106
- value = torch.tensor(self.target_prediction_time, **self._tkwargs).unsqueeze(0)
106
+ value = torch.tensor(self.target_prediction_time, **self.tkwargs).unsqueeze(0)
107
107
  fixed_acq = FixedFeatureAcquisitionFunction(
108
108
  acq, self.vocs.n_variables + 1, column, value
109
109
  )
@@ -1,11 +1,12 @@
1
1
  import logging
2
2
  import math
3
3
  from abc import ABC, abstractmethod
4
- from typing import Dict, Optional, Union
4
+ from typing import Dict, Optional
5
5
 
6
6
  import pandas as pd
7
7
  import torch
8
8
  from pydantic import ConfigDict, Field, PositiveFloat, PositiveInt
9
+ from torch import Tensor
9
10
 
10
11
  from xopt.pydantic import XoptBaseModel
11
12
  from xopt.vocs import VOCS
@@ -51,7 +52,6 @@ class TurboController(XoptBaseModel, ABC):
51
52
  restrict_model_data: Optional[bool] = Field(
52
53
  True, description="flag to restrict model data to within the trust region"
53
54
  )
54
- tkwargs: Dict[str, Union[torch.dtype, str]] = Field({"dtype": torch.double})
55
55
 
56
56
  model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)
57
57
 
@@ -79,9 +79,24 @@ class TurboController(XoptBaseModel, ABC):
79
79
  )
80
80
  )
81
81
 
82
- def get_trust_region(self, generator):
82
+ def get_trust_region(self, generator) -> Tensor:
83
+ """
84
+
85
+ Return the trust region based on the generator. The trust region is a
86
+ rectangular region around a center point. The sides of the trust region are
87
+ given by the `length` parameter and are scaled according to the generator
88
+ model lengthscales (if available).
89
+
90
+ Parameters
91
+ ----------
92
+ generator : BayesianGenerator
93
+ Generator object used to supply the model and datatypes for the returned
94
+ trust region.
95
+
96
+ """
97
+
83
98
  model = generator.model
84
- bounds = torch.tensor(self.vocs.bounds, **self.tkwargs)
99
+ bounds = torch.tensor(self.vocs.bounds, **generator.tkwargs)
85
100
 
86
101
  if self.center_x is not None:
87
102
  # get bounds width
@@ -89,18 +104,22 @@ class TurboController(XoptBaseModel, ABC):
89
104
 
90
105
  # Scale the TR to be proportional to the lengthscales of the objective model
91
106
  x_center = torch.tensor(
92
- [self.center_x[ele] for ele in self.vocs.variable_names], **self.tkwargs
107
+ [self.center_x[ele] for ele in self.vocs.variable_names],
108
+ **generator.tkwargs,
93
109
  ).unsqueeze(dim=0)
94
110
 
95
- if model.models[0].covar_module.base_kernel.lengthscale is not None:
96
- lengthscales = model.models[
97
- 0
98
- ].covar_module.base_kernel.lengthscale.detach()
111
+ # default weights are 1 (if there is no model or a model without
112
+ # lengthscales)
113
+ weights = 1.0
99
114
 
100
- # calculate the ratios of lengthscales for each axis
101
- weights = lengthscales / torch.prod(lengthscales) ** (1 / self.dim)
102
- else:
103
- weights = 1.0
115
+ if model is not None:
116
+ if model.models[0].covar_module.base_kernel.lengthscale is not None:
117
+ lengthscales = model.models[
118
+ 0
119
+ ].covar_module.base_kernel.lengthscale.detach()
120
+
121
+ # calculate the ratios of lengthscales for each axis
122
+ weights = lengthscales / torch.prod(lengthscales) ** (1 / self.dim)
104
123
 
105
124
  # calculate the tr bounding box
106
125
  tr_lb = torch.clamp(
@@ -1,3 +1,4 @@
1
+ from copy import deepcopy
1
2
  from typing import List
2
3
 
3
4
  import numpy as np
@@ -206,16 +207,19 @@ def validate_turbo_controller_base(value, available_controller_types, info):
206
207
  f"{available_controller_types.keys()}"
207
208
  )
208
209
  elif isinstance(value, dict):
210
+ value_copy = deepcopy(value)
209
211
  # create turbo controller from dict input
210
212
  if "name" not in value:
211
213
  raise ValueError("turbo input dict needs to have a `name` attribute")
212
- name = value.pop("name")
214
+ name = value_copy.pop("name")
213
215
  if name in available_controller_types:
214
216
  # pop unnecessary elements
215
217
  for ele in ["dim"]:
216
- value.pop(ele, None)
218
+ value_copy.pop(ele, None)
217
219
 
218
- value = available_controller_types[name](vocs=info.data["vocs"], **value)
220
+ value = available_controller_types[name](
221
+ vocs=info.data["vocs"], **value_copy
222
+ )
219
223
  else:
220
224
  raise ValueError(
221
225
  f"{value} not found, available values are "
@@ -123,7 +123,8 @@ class CNSGAGenerator(Generator):
123
123
  return
124
124
 
125
125
  if filename is None:
126
- filename = f"{self.name}_offspring_{xopt.utils.isotime(include_microseconds=True)}.csv"
126
+ timestamp = xopt.utils.isotime(include_microseconds=True).replace(":", "_")
127
+ filename = f"{self.name}_offspring_{timestamp}.csv"
127
128
  filename = os.path.join(self.output_path, filename)
128
129
 
129
130
  self._offspring.to_csv(filename, index_label="xopt_index")
@@ -139,7 +140,8 @@ class CNSGAGenerator(Generator):
139
140
  return
140
141
 
141
142
  if filename is None:
142
- filename = f"{self.name}_population_{xopt.utils.isotime(include_microseconds=True)}.csv"
143
+ timestamp = xopt.utils.isotime(include_microseconds=True).replace(":", "_")
144
+ filename = f"{self.name}_population_{timestamp}.csv"
143
145
  filename = os.path.join(self.output_path, filename)
144
146
 
145
147
  self.population.to_csv(filename, index_label="xopt_index")
@@ -15,7 +15,9 @@ class NumericalOptimizer(XoptBaseModel, ABC):
15
15
  model_config = ConfigDict(extra="forbid")
16
16
 
17
17
  @abstractmethod
18
- def optimize(self, function: AcquisitionFunction, bounds: Tensor, n_candidates=1):
18
+ def optimize(
19
+ self, function: AcquisitionFunction, bounds: Tensor, n_candidates=1, **kwargs
20
+ ):
19
21
  """optimize a function to produce a number of candidate points that
20
22
  minimize the function"""
21
23
  pass
@@ -33,7 +35,7 @@ class LBFGSOptimizer(NumericalOptimizer):
33
35
 
34
36
  model_config = ConfigDict(validate_assignment=True)
35
37
 
36
- def optimize(self, function, bounds, n_candidates=1):
38
+ def optimize(self, function, bounds, n_candidates=1, **kwargs):
37
39
  assert isinstance(bounds, Tensor)
38
40
  if len(bounds) != 2:
39
41
  raise ValueError("bounds must have the shape [2, ndim]")
@@ -46,6 +48,7 @@ class LBFGSOptimizer(NumericalOptimizer):
46
48
  num_restarts=self.n_restarts,
47
49
  timeout_sec=self.max_time,
48
50
  options={"maxiter": self.max_iter},
51
+ **kwargs,
49
52
  )
50
53
  return candidates
51
54
 
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+
3
+ from xopt import VOCS
4
+
5
+
6
+ def construct_zdt(n_dims, problem_index=1):
7
+ """construct Xopt versions of the multiobjective ZDT test problems"""
8
+ vocs = VOCS(
9
+ variables={f"x{i + 1}": [0, 1] for i in range(n_dims)},
10
+ objectives={"f1": "MINIMIZE", "f2": "MINIMIZE"},
11
+ )
12
+
13
+ if problem_index == 1:
14
+ # ZDT1
15
+ def evaluate(input_dict):
16
+ x = np.array([input_dict[f"x{i + 1}"] for i in range(n_dims)])
17
+
18
+ f1 = x[0]
19
+ g = 1 + (9 / (n_dims - 1)) * np.sum(x[1:])
20
+ h = 1 - np.sqrt(f1 / g)
21
+ f2 = g * h
22
+
23
+ return {"f1": f1, "f2": f2, "g": g}
24
+ elif problem_index == 2:
25
+
26
+ def evaluate(input_dict):
27
+ x = np.array([input_dict[f"x{i + 1}"] for i in range(n_dims)])
28
+
29
+ f1 = x[0]
30
+ g = 1 + (9 / (n_dims - 1)) * np.sum(x[1:])
31
+ h = 1 - (f1 / g) ** 2
32
+ f2 = g * h
33
+
34
+ return {"f1": f1, "f2": f2, "g": g}
35
+ elif problem_index == 3:
36
+
37
+ def evaluate(input_dict):
38
+ x = np.array([input_dict[f"x{i + 1}"] for i in range(n_dims)])
39
+
40
+ f1 = x[0]
41
+ g = 1 + (9 / (n_dims - 1)) * np.sum(x[1:])
42
+ h = 1 - np.sqrt(f1 / g) - (f1 / g) * np.sin(10 * np.pi * f1)
43
+ f2 = g * h
44
+
45
+ return {"f1": f1, "f2": f2, "g": g}
46
+ else:
47
+ raise NotImplementedError()
48
+
49
+ reference_point = {"f1": 1.0, "f2": 1.0}
50
+
51
+ return vocs, evaluate, reference_point