Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions doc/source/user_guide/basic_usage/basic_setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ Using an external generator
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have a generator from a third-party library that follows the
`gest-api <https://github.com/campa-consortium/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`).
you can pass it directly to :class:`~optimas.explorations.Exploration`. The
external generator must be instantiated and configured first. The external
library itself must be installed separately (see :ref:`dependencies`).

``ExternalGenerator`` remains available as a deprecated compatibility wrapper
for applications that need to provide Optimas-specific generator options.

Known libraries containing generators compatible with this interface include
`Xopt <https://github.com/xopt-org/Xopt>`_ and `libEnsemble
Expand All @@ -85,7 +86,6 @@ 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

Expand All @@ -94,14 +94,12 @@ Using a generic ``gest-api``-compatible generator:
objectives={"f": "MINIMIZE"},
)

ext_gen = SomeGenerator(vocs=vocs)
gen = ExternalGenerator(ext_gen=ext_gen, vocs=vocs)
gen = SomeGenerator(vocs=vocs)

Using an `Xopt <https://github.com/xopt-org/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,
Expand All @@ -113,11 +111,8 @@ Using an `Xopt <https://github.com/xopt-org/Xopt>`_ generator specifically:
)

# 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)
gen = ExpectedImprovementGenerator(vocs=vocs)
gen.ingest([{"x0": 1.0, "x1": 0.5, "f": 3.2}])


Evaluator
Expand Down
3 changes: 2 additions & 1 deletion optimas/explorations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from optimas.core.trial import TrialStatus
from optimas.generators.base import Generator
from optimas.generators.external import adapt_generator
from optimas.evaluators.base import Evaluator
from optimas.evaluators.function_evaluator import FunctionEvaluator
from optimas.utils.logger import get_logger
Expand Down Expand Up @@ -104,7 +105,7 @@ def __init__(
"'threads' mode is only supported when using a "
"`FunctionEvaluator`. Use 'local' mode instead."
)
self.generator = generator
self.generator = adapt_generator(generator)
self.evaluator = evaluator
self.max_evals = max_evals
self.sim_workers = sim_workers
Expand Down
131 changes: 53 additions & 78 deletions optimas/generators/external.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,100 +5,75 @@
(https://github.com/campa-consortium/gest-api) into Optimas.
"""

from .base import Generator


class ExternalGenerator(Generator):
"""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 <https://github.com/xopt-org/Xopt>`_ and `libEnsemble
<https://github.com/Libensemble/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:
import warnings

.. code-block:: python
from gest_api.generator import Generator as StandardGenerator

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 <https://github.com/xopt-org/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"},
)
from .base import Generator

# 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)
class _ExternalGeneratorAdapter(Generator):
"""Adapt a ``gest-api`` generator to the Optimas generator protocol.

ev = FunctionEvaluator(function=my_function)
exp = Exploration(generator=gen, evaluator=ev, max_evals=20, sim_workers=4)
exp.run()
The adapter supplies Optimas trial bookkeeping while delegating the
standardized ``suggest`` and ``ingest`` operations to the wrapped object.
"""

def __init__(
self,
ext_gen,
vocs=None,
**kwargs,
):
super().__init__(
**kwargs,
)
if not isinstance(ext_gen, StandardGenerator):
raise TypeError(
"ext_gen must implement the gest-api Generator interface."
)
if vocs is None:
try:
vocs = ext_gen.vocs
except AttributeError as exc:
raise ValueError(
"The external generator must expose a `vocs` attribute, "
"or `vocs` must be provided explicitly."
) from exc
super().__init__(vocs=vocs, **kwargs)
self.gen = ext_gen

def suggest(self, n_trials):
"""Request the next set of points to evaluate.

Delegates to the wrapped generator's ``suggest`` method.
"""
"""Request the next set of points to evaluate."""
return self.gen.suggest(n_trials)

def ingest(self, trials):
"""Send the results of evaluations to the generator.

Delegates to the wrapped generator's ``ingest`` method.
"""
"""Send the results of evaluations to the generator."""
self.gen.ingest(trials)


def adapt_generator(generator, **kwargs):
"""Adapt a gest-api generator to the Optimas generator protocol."""
if isinstance(generator, Generator):
return generator
if isinstance(generator, StandardGenerator):
return _ExternalGeneratorAdapter(generator, **kwargs)
raise TypeError(
"generator must be an Optimas Generator or implement the gest-api "
"Generator interface."
)


class ExternalGenerator(_ExternalGeneratorAdapter):
"""Deprecated compatibility wrapper for a gest-api generator.

Pass a gest-api generator directly to :class:`~optimas.explorations.Exploration`
instead. This class remains available for compatibility and supports the
Optimas-specific generator options accepted by the previous wrapper.
"""

def __init__(self, ext_gen, **kwargs):
warnings.warn(
"ExternalGenerator is deprecated. Pass the gest-api generator "
"directly to Exploration instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(ext_gen=ext_gen, **kwargs)
17 changes: 2 additions & 15 deletions tests/test_xopt_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import numpy as np

from optimas.generators import ExternalGenerator
from xopt.generators.bayesian.expected_improvement import (
ExpectedImprovementGenerator,
)
Expand Down Expand Up @@ -39,6 +38,7 @@ def test_xopt_EI():
objectives={"y1": "MINIMIZE"},
)

# Create generator. gest-api generators are compatible.
gen = ExpectedImprovementGenerator(vocs=vocs)

# Create 4 initial points and ingest them
Expand All @@ -50,13 +50,6 @@ def test_xopt_EI():
]
gen.ingest(initial_points)

# Create generator.
gen = ExternalGenerator(
ext_gen=gen,
vocs=vocs,
save_model=True,
)

# Create evaluator.
ev = FunctionEvaluator(function=xtest)

Expand Down Expand Up @@ -89,6 +82,7 @@ def test_xopt_neldermead():
objectives={"y1": "MINIMIZE"},
)

# Create generator. gest-api generators are compatible.
gen = NelderMeadGenerator(vocs=vocs)

# Create 4 initial points and ingest them
Expand All @@ -99,13 +93,6 @@ def test_xopt_neldermead():
]
gen.ingest(initial_points)

# Create generator.
gen = ExternalGenerator(
ext_gen=gen,
vocs=vocs,
save_model=True,
)

# Create evaluator.
ev = FunctionEvaluator(function=rosenbrock)

Expand Down
Loading