diff --git a/doc/source/api/generators.rst b/doc/source/api/generators.rst index d8014c3c..3dca4324 100644 --- a/doc/source/api/generators.rst +++ b/doc/source/api/generators.rst @@ -15,3 +15,4 @@ Generators AxMultiFidelityGenerator AxMultitaskGenerator AxClientGenerator + ExternalGenerator diff --git a/doc/source/user_guide/basic_usage/basic_setup.rst b/doc/source/user_guide/basic_usage/basic_setup.rst index 30234d22..6dee3b8f 100644 --- a/doc/source/user_guide/basic_usage/basic_setup.rst +++ b/doc/source/user_guide/basic_usage/basic_setup.rst @@ -81,6 +81,59 @@ Bayesian optimization loop is started (see ) +Using an external generator +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you have a generator from a third-party library that follows the +`gest-api `_ generator standard, +you can integrate it with Optimas using +:class:`~optimas.generators.ExternalGenerator`. The external generator must be +instantiated and configured first, then passed to ``ExternalGenerator`` as a +wrapper. The external library itself must be installed separately (see +:ref:`dependencies`). + +Known libraries containing generators compatible with this interface include +`Xopt `_ and `libEnsemble +`_. + +Using a generic ``gest-api``-compatible generator: + +.. code-block:: python + + from optimas.generators import ExternalGenerator + from gest_api.vocs import VOCS + from some_library import SomeGenerator + + vocs = VOCS( + variables={"x0": [0.0, 15.0], "x1": [-5.0, 5.0]}, + objectives={"f": "MINIMIZE"}, + ) + + ext_gen = SomeGenerator(vocs=vocs) + gen = ExternalGenerator(ext_gen=ext_gen, vocs=vocs) + +Using an `Xopt `_ generator specifically: + +.. code-block:: python + + from optimas.generators import ExternalGenerator + from gest_api.vocs import VOCS + from xopt.generators.bayesian.expected_improvement import ( + ExpectedImprovementGenerator, + ) + + vocs = VOCS( + variables={"x0": [0.0, 15.0], "x1": [-5.0, 5.0]}, + objectives={"f": "MINIMIZE"}, + ) + + # Create and (optionally) pre-seed the external generator. + ext_gen = ExpectedImprovementGenerator(vocs=vocs) + ext_gen.ingest([{"x0": 1.0, "x1": 0.5, "f": 3.2}]) + + # Wrap it for use with optimas. + gen = ExternalGenerator(ext_gen=ext_gen, vocs=vocs) + + Evaluator ~~~~~~~~~ The evaluator is in charge of getting the trials suggested by the generator and diff --git a/doc/source/user_guide/dependencies.rst b/doc/source/user_guide/dependencies.rst index cf71ed89..182a84c3 100644 --- a/doc/source/user_guide/dependencies.rst +++ b/doc/source/user_guide/dependencies.rst @@ -44,3 +44,12 @@ See table below for a summary. * - :class:`~optimas.generators.AxClientGenerator` - :math:`\times` - :math:`\checkmark` + * - :class:`~optimas.generators.ExternalGenerator` [#external]_ + - :math:`\checkmark` + - :math:`\checkmark` + +.. [#external] :class:`~optimas.generators.ExternalGenerator` itself has no + additional Optimas dependency beyond ``gest-api`` (already a core + requirement). However, the external generator library being wrapped + (e.g., `Xopt generators `_, or libEnsemble's + `APOSMM `_. diff --git a/optimas/generators/external.py b/optimas/generators/external.py index 36a1a38c..4c67083c 100644 --- a/optimas/generators/external.py +++ b/optimas/generators/external.py @@ -1,10 +1,83 @@ -"""Contains the definition of an external generator.""" +"""Contains the definition of ExternalGenerator. + +This module provides a wrapper that integrates third-party generators +implementing the ``gest-api`` generator standard +(https://github.com/campa-consortium/gest-api) into Optimas. +""" from .base import Generator class ExternalGenerator(Generator): - """Supports a generator in the CAMPA generator standard.""" + """Wrap a third-party generator that follows the ``gest-api`` standard: + + https://github.com/campa-consortium/gest-api + + Any external generator that implements this interface can be used inside + optimas by wrapping it in ``ExternalGenerator``. + + Known libraries containing generators compatible with this interface include + `Xopt `_ and `libEnsemble + `_. + + Parameters + ---------- + ext_gen : object + An object implementing/sub-classing the ``gest-api`` generator interface. The + external generator should be fully configured (including any initial + data ingested) before being passed here. The external library itself + must be installed separately. + **kwargs + Additional keyword arguments forwarded to the base + :class:`~optimas.generators.Generator` (e.g., ``vocs``, + ``save_model``). + + Examples + -------- + Using a generic ``gest-api``-compatible generator: + + .. code-block:: python + + from optimas.generators import ExternalGenerator + from gest_api.vocs import VOCS + from some_library import SomeGenerator + + vocs = VOCS( + variables={"x1": [0.0, 1.0], "x2": [0.0, 10.0]}, + objectives={"y1": "MINIMIZE"}, + ) + + ext_gen = SomeGenerator(vocs=vocs) + gen = ExternalGenerator(ext_gen=ext_gen, vocs=vocs) + + Using an `Xopt `_ generator: + + .. code-block:: python + + from optimas.generators import ExternalGenerator + from optimas.evaluators import FunctionEvaluator + from optimas.explorations import Exploration + from gest_api.vocs import VOCS + from xopt.generators.bayesian.expected_improvement import ( + ExpectedImprovementGenerator, + ) + + vocs = VOCS( + variables={"x1": [0.0, 1.0], "x2": [0.0, 10.0]}, + objectives={"y1": "MINIMIZE"}, + ) + + # Create and (optionally) pre-seed the external generator. + ext_gen = ExpectedImprovementGenerator(vocs=vocs) + ext_gen.ingest([{"x1": 0.5, "x2": 5.0, "y1": 5.0}]) + + # Wrap it for use with optimas. + gen = ExternalGenerator(ext_gen=ext_gen, vocs=vocs) + + ev = FunctionEvaluator(function=my_function) + exp = Exploration(generator=gen, evaluator=ev, max_evals=20, sim_workers=4) + exp.run() + """ def __init__( self, @@ -17,9 +90,15 @@ def __init__( self.gen = ext_gen def suggest(self, n_trials): - """Request the next set of points to evaluate.""" + """Request the next set of points to evaluate. + + Delegates to the wrapped generator's ``suggest`` method. + """ return self.gen.suggest(n_trials) def ingest(self, trials): - """Send the results of evaluations to the generator.""" + """Send the results of evaluations to the generator. + + Delegates to the wrapped generator's ``ingest`` method. + """ self.gen.ingest(trials)