diff --git a/.github/environment.yml b/.github/environment.yml
index 0411e61dd..577c99079 100644
--- a/.github/environment.yml
+++ b/.github/environment.yml
@@ -21,3 +21,4 @@ dependencies:
# testing
- parameterized
- testflo
+ - egobox >=0.37.6, <0.38.0
diff --git a/doc/index.rst b/doc/index.rst
index c9d1b0fd7..63c9dfe41 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -14,7 +14,7 @@ The list of supported optimizers is shown on the sidebar to the left.
Of those, the following are not installed by default:
- SNOPT and NLPQLP are proprietary and must be obtained from their respective authors
-- IPOPT and ParOpt must be installed separately
+- IPOPT, ParOpt and Egor must be installed separately
pyOptSparse is a fork of `pyOpt `_.
@@ -70,3 +70,4 @@ To get started, please see the :ref:`install` and the :ref:`quickstart`.
optimizers/CONMIN
optimizers/ALPSO
optimizers/UNO
+ optimizers/Egor
diff --git a/doc/optimizers/Egor.rst b/doc/optimizers/Egor.rst
new file mode 100644
index 000000000..2559dd3bd
--- /dev/null
+++ b/doc/optimizers/Egor.rst
@@ -0,0 +1,50 @@
+.. _egor:
+
+Egor
+====
+
+Egor is the surrogate-based Efficient Global Optimization (EGO) optimizer from the open-source
+`EGObox `_ library.
+
+Egor uses `bayesian optimization `_ techniques
+well-suited to find the global optimum of an expansive-to-evaluate black-box function.
+Basically, it uses a surrogate model to approximate the objective function and
+an infill criterion (aka acquisition function) to guide the search for the optimum.
+
+The pyOptSparse wrapper is derivative-free and targets single-objective, bounded,
+continuous design spaces. Constraint values are passed to Egor with the pyOptSparse
+constraint convention transformed to :math:`c(x) \le 0`.
+
+Installation
+------------
+
+Egor is made available through the `egobox `_ Python package.
+
+.. prompt::
+
+ $ pip install egobox
+
+``egobox`` is also available via conda-forge::
+
+ $ conda install -c conda-forge egobox
+
+
+Options
+-------
+
+Please refer to the Egor help for a complete listing of options and their default values.
+
+.. prompt::
+
+ $ python
+ >>> import egobox as egx
+ >>> help(egx.Egor)
+ >>> help(egx.GpConfig)
+
+pyoptSparse expects pickable objects while native Egor structures as GpConfig are not pickable.
+To workaround this constraint, the pyOptSparse Egor wrapper uses dictionaries which are accepted by Egor
+to update the default field values of Egor structures.
+Names and default values of the fields are provided in the descriptions below.
+
+.. optionstable:: pyoptsparse.pyEgor.pyEgor.Egor
+ :filename: Egor_options.yaml
diff --git a/doc/optimizers/Egor_options.yaml b/doc/optimizers/Egor_options.yaml
new file mode 100644
index 000000000..0b497ecb6
--- /dev/null
+++ b/doc/optimizers/Egor_options.yaml
@@ -0,0 +1,98 @@
+gp_config:
+ desc: |
+ GpConfig as a dict used by Egor for surrogate model configuration.
+ Main defaults are:
+
+ - ``regr_spec``: 1 (Constant)
+ - ``corr_spec``: 2 (Squared Exponential)
+ - ``kpls_dim``: no PLS dimensionality reduction (otherwise int)
+ - ``n_clusters``: no clustering (otherwise int)
+cstr_tol:
+ desc: |
+ Constraint tolerances list passed to Egor (size n_cstr plus n_fcstr)
+ Default is ``1e-4`` for all constraints.
+n_start:
+ desc: Number of infill optimization runs (best run selected)
+n_doe:
+ desc: Number of initial DOE samples (0 lets Egor choose automatically)
+doe:
+ desc: |
+ Initial DOE array, either x-only or concatenated x and y
+ to be passed as list of lists of floats. If not provided, Egor will generate a DOE automatically.
+infill_strategy:
+ desc: |
+ Infill criterion:
+
+ - 1 = ``EI``,
+ - 2 = ``WB2``,
+ - 3 = ``WB2S``,
+ - 4 = ``LOG_EI`` (default)
+cstr_infill:
+ desc: Enable constrained infill criterion (aka CEI)
+cstr_strategy:
+ desc: |
+ Constraint strategy enum for surrogate constraint handling:
+
+ - 1 = ``MeanConstraint`` (default)
+ - 2 = ``UpperConfidenceBound``
+qei_config:
+ desc: |
+ QEiConfig for batch (qEI) point selection passed as a dict with main keys being:
+
+ - ``batch``: size of batch (int)
+ - ``strategy``: qEI strategy enum
+infill_optimizer:
+ desc: |
+ Internal infill optimizer:
+
+ - 1 = ``COBYLA`` (default)
+ - 2 = ``SLSQP``
+trego:
+ desc: |
+ Enable TREGO (aka Trust Region EGO) algorithm configured with main parameters:
+
+ - ``n_gl_steps``: (nb of global search steps default 1, nb of local search steps default 4)
+ - ``beta``: trust region factor (default 0.9)
+
+coego_n_coop:
+ desc: Number of cooperative groups for CoEGO algorithm
+target:
+ desc: Known objective target used as stopping criterion
+outdir:
+ desc: Output directory for Egor output files (configuration, does, history and warm start search)
+warm_start:
+ desc: Load initial DOE from outdir when enabled
+hot_start:
+ desc: |
+ Egor checkpoint restart parameter to be used in case of fallible environment
+ to continue with the same Egor parameterization till max iterations or timeout is reached.
+failsafe_strategy:
+ desc: |
+ Failure handling enum
+
+ - 1 = ``REJECTION`` (default),
+ - 2 = ``IMPUTATION``,
+ - 3 = ``PROBA OF VIABILITY``
+seed:
+ desc: Seed for random number generator (default -1 for random seed)
+verbose:
+ desc: |
+ Verbosity level for Egor logging
+
+ - 0 = ``error`` (default)
+ - 1 = ``warning``
+ - 2 = ``info``
+ - 3 = ``debug``
+max_iters:
+ desc: Egor iteration budget
+run_info:
+ desc: Optional RunInfo used to pass additional information to Egor (e.g., for logging)
+timeout:
+ desc: Optional timeout in seconds used as sttopping criterion for Egor minimize
+fcstrs:
+ desc: |
+ Optional list of native Egobox function constraints passed directly as fcstrs
+ (instead of pyOptSparse constraints which are metamodelized)
+fcstr_specs:
+ desc: |
+ Optional list of egobox.CstrSpec for function constraints passed as fcstrs
diff --git a/pyoptsparse/__init__.py b/pyoptsparse/__init__.py
index 134a5a4fc..ff3428054 100644
--- a/pyoptsparse/__init__.py
+++ b/pyoptsparse/__init__.py
@@ -21,6 +21,7 @@
from .pyNSGA2.pyNSGA2 import NSGA2
from .pyALPSO.pyALPSO import ALPSO
from .pyParOpt.ParOpt import ParOpt
+from .pyEgor.pyEgor import Egor
__all__ = [
"History",
@@ -43,6 +44,7 @@
"NSGA2",
"ALPSO",
"ParOpt",
+ "Egor",
"testing",
"list_optimizers",
]
diff --git a/pyoptsparse/pyEgor/__init__.py b/pyoptsparse/pyEgor/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/pyoptsparse/pyEgor/pyEgor.py b/pyoptsparse/pyEgor/pyEgor.py
new file mode 100644
index 000000000..fbe23a1d9
--- /dev/null
+++ b/pyoptsparse/pyEgor/pyEgor.py
@@ -0,0 +1,248 @@
+"""
+pyEgor - A pyOptSparse interface to Egor from egobox.
+"""
+
+# Standard Python modules
+import datetime
+import time
+
+# External modules
+import numpy as np
+
+# Local modules
+from ..pyOpt_optimizer import Optimizer
+from ..pyOpt_solution import SolutionInform
+from ..pyOpt_utils import import_module
+
+# import the Python module
+egobox = import_module("egobox")
+
+
+class Egor(Optimizer):
+ """
+ Egor Optimizer Class - wrapper for the Egor global optimization algorithm from egobox.
+ """
+
+ def __init__(self, raiseError=True, options=None):
+ if options is None:
+ options = {}
+ name = "Egor"
+ category = "Global Optimizer"
+ defOpts = self._getDefaultOptions()
+ informs = self._getInforms()
+ super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options)
+
+ if isinstance(egobox, Exception) and raiseError:
+ raise egobox
+
+ @staticmethod
+ def _getInforms():
+ informs = {
+ 1: "Reached maximum number of iterations",
+ 2: "Reached target cost function value",
+ 3: "Algorithm manually interrupted with SIGINT (Ctrl+C), SIGTERM or SIGHUP",
+ 4: "Algorithm peek at the same point twice. We consider it is converged.",
+ 5: "Timeout reached",
+ 6: "Solver unexpected exit. See logs for details.",
+ }
+ return informs
+
+ @staticmethod
+ def _getDefaultOptions():
+ defOpts = {
+ "gp_config": [dict, dict()], # GpConfig as a dict used by Egor for surrogate model configuration
+ "cstr_tol": [list, []],
+ "n_start": [int, 20],
+ "n_doe": [int, 0],
+ "doe": [list, [[]]],
+ "infill_strategy": [int, 4], # default to LOG_EI
+ "cstr_infill": [bool, False],
+ "cstr_strategy": [int, 1], # default to MC
+ "qei_config": [dict, dict()],
+ "infill_optimizer": [int, 1], # default to COBYLA
+ "trego": [dict, dict()],
+ "coego_n_coop": [int, 0],
+ "target": [float, -1e12],
+ "outdir": [str, ""],
+ "warm_start": [bool, False],
+ "hot_start": [bool, False],
+ "failsafe_strategy": [int, 1], # default to REJECTION
+ "seed": [int, -1],
+ "verbose": [int, 0], # level of verbosity, 0 = error, 1 = warn, 2 = info, 3 = debug
+ "max_iters": [int, 20],
+ "run_info": [dict, dict()],
+ "timeout": [float, -1.0],
+ "fcstrs": [list, []],
+ "fcstr_specs": [list, []],
+ }
+ return defOpts
+
+ def __call__(self, optProb, storeHistory=None, hotStart=None, **kwargs):
+ """
+ Solve optimization problem using Egor from egobox.
+
+ Parameters
+ ----------
+ optProb : Optimization or Solution class instance
+ This is the complete description of the optimization problem
+ to be solved by the optimizer
+
+ storeHistory : str
+ File name of the history file into which the history of
+ this optimization will be stored
+
+ hotStart : str
+ File name of the history file to "replay" for the
+ optimization. The optimization problem used to generate
+ the history file specified in 'hotStart' must be
+ **IDENTICAL** to the currently supplied 'optProb'. By
+ identical we mean, **EVERY SINGLE PARAMETER MUST BE
+ IDENTICAL**. As soon as he requested evaluation point
+ from NSGA2 does not match the history, function and
+ gradient evaluations revert back to normal evaluations.
+
+ Notes
+ -----
+ The kwargs are present for compatibility with other optimizers.
+ Any sensitivity settings are ignored because Egor is derivative-free.
+ """
+ self.startTime = time.time()
+ self.callCounter = 0
+
+ # Save the optimization problem and finalize constraint Jacobian
+ self.optProb = optProb
+ self.optProb.finalize()
+
+ # Egor currently supports a single objective in this wrapper.
+ if len(self.optProb.objectives) != 1:
+ raise ValueError("Egor wrapper currently supports single-objective problems only.")
+
+ # Set history/hotstart/coldstart
+ self._setHistory(storeHistory, hotStart)
+ self._setInitialCacheValues()
+
+ if len(optProb.constraints) == 0:
+ self.unconstrained = True
+
+ blx, bux, xs = self._assembleContinuousVariables()
+ xs = np.maximum(xs, blx)
+ xs = np.minimum(xs, bux)
+ n = len(xs)
+
+ if np.any(~np.isfinite(blx)) or np.any(~np.isfinite(bux)):
+ raise ValueError("Egor requires finite lower and upper bounds for all design variables.")
+
+ # Determine the number of constraints and set up constraint information
+ if self.unconstrained:
+ n_cstr = 0
+ else:
+ indices, blc, buc, fact = self.optProb.getOrdering(["ne", "le", "ni", "li"], oneSided=True, noEquality=True)
+ n_cstr = len(indices)
+ self.optProb.jacIndices = indices
+ self.optProb.fact = fact
+ self.optProb.offset = buc
+
+ if self.optProb.comm.rank == 0:
+ opt = self.getOption
+
+ # Build x specifications from pyOptSparse bounds.
+ xspecs = [egobox.XSpec(egobox.XType.FLOAT, [float(blx[i]), float(bux[i])]) for i in range(n)]
+
+ gp_config = opt("gp_config")
+ infill_strategy = opt("infill_strategy")
+ cstr_strategy = opt("cstr_strategy")
+ qei_config = opt("qei_config")
+ infill_optimizer = opt("infill_optimizer")
+ failsafe_strategy = opt("failsafe_strategy")
+
+ fcstrs_opt = opt("fcstrs")
+ fcstr_specs = opt("fcstr_specs")
+
+ n_fcstrs = 0 if fcstrs_opt is None else len(fcstrs_opt)
+ if fcstr_specs is not None and len(fcstr_specs) not in (0, n_fcstrs):
+ raise ValueError(
+ "Option 'fcstr_specs' length must be zero or match the number of function constraints."
+ )
+
+ # Prepare the constructor kwargs for Egor.
+ ctor_kwargs = {
+ "gp_config": gp_config,
+ "n_cstr": n_cstr,
+ "cstr_tol": opt("cstr_tol") if len(opt("cstr_tol")) > 0 else None,
+ "n_start": opt("n_start"),
+ "n_doe": opt("n_doe"),
+ "doe": np.array(opt("doe")) if np.array(opt("doe")).size > 0 else None,
+ "infill_strategy": infill_strategy,
+ "cstr_infill": opt("cstr_infill"),
+ "cstr_strategy": cstr_strategy,
+ "qei_config": qei_config,
+ "infill_optimizer": infill_optimizer,
+ "trego": opt("trego") if opt("trego") else None,
+ "coego_n_coop": opt("coego_n_coop"),
+ "target": float(opt("target")),
+ "failsafe_strategy": failsafe_strategy,
+ }
+ solver = egobox.Egor(xspecs, **ctor_kwargs)
+
+ # Adapt the objective and constraint function to the Egor interface.
+ def fun(x):
+ x_eval = np.atleast_2d(np.asarray(x, dtype=float))
+ ncols = 1 + n_cstr
+ y = np.zeros((x_eval.shape[0], ncols), dtype=float)
+ for i in range(x_eval.shape[0]):
+ xi = np.clip(x_eval[i], blx, bux)
+ fobj, fcon, fail = self._masterFunc(xi, ["fobj", "fcon"])
+ if fail:
+ y[i, :] = np.nan
+ continue
+ y[i, 0] = float(np.atleast_1d(fobj)[0])
+ if n_cstr > 0:
+ y[i, 1:] = np.asarray(fcon, dtype=float)
+ return y
+
+ fcstrs = [] if fcstrs_opt is None else list(fcstrs_opt)
+
+ # Prepare the minimize kwargs for Egor minimize.
+ minimize_kwargs = {
+ "fcstrs": fcstrs,
+ "fcstr_specs": [] if fcstr_specs is None else fcstr_specs,
+ "max_iters": opt("max_iters"),
+ "run_info": opt("run_info"),
+ "outdir": opt("outdir") if opt("outdir") != "" else None,
+ "warm_start": opt("warm_start"),
+ "hot_start": True if opt("hot_start") else False,
+ "seed": opt("seed") if opt("seed") >= 0 else None,
+ "timeout": float(opt("timeout")) if opt("timeout") > 0 else None,
+ "verbose": opt("verbose"),
+ }
+
+ t0 = time.time()
+ egor_result = solver.minimize(fun, **minimize_kwargs)
+ optTime = time.time() - t0
+
+ if self.storeHistory:
+ self.metadata["endTime"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ self.metadata["optTime"] = optTime
+ self.hist.writeData("metadata", self.metadata)
+ self.hist.close()
+
+ # Broadcast a -1 to indicate optimization has finished
+ self.optProb.comm.bcast(-1, root=0)
+
+ # Optimizer has no standardized exit code mapping in this wrapper.
+ sol_inform = SolutionInform.from_informs(self.informs, int(egor_result.status.exit))
+
+ result = egor_result.result
+
+ xstar = np.asarray(result.x_opt, dtype=float).reshape(-1)
+ ystar = np.asarray(result.y_opt, dtype=float).reshape(-1)
+ fstar = float(ystar[0])
+
+ # Create the optimization solution
+ sol = self._createSolution(optTime, sol_inform, fstar, xstar)
+ else:
+ self._waitLoop()
+ sol = None
+
+ sol = self._communicateSolution(sol)
+ return sol
diff --git a/pyoptsparse/pyOpt_optimizer.py b/pyoptsparse/pyOpt_optimizer.py
index 2a13d14ce..b3f723dcc 100644
--- a/pyoptsparse/pyOpt_optimizer.py
+++ b/pyoptsparse/pyOpt_optimizer.py
@@ -973,7 +973,7 @@ def getInform(self, infocode: int | None = None) -> str | dict[int, str]:
# =============================================================================
# List of optimizers as an enum
-Optimizers = Enum("Optimizers", "SNOPT IPOPT Uno SLSQP NLPQLP CONMIN NSGA2 PSQP ALPSO ParOpt")
+Optimizers = Enum("Optimizers", "SNOPT IPOPT Uno SLSQP NLPQLP CONMIN NSGA2 PSQP ALPSO ParOpt Egor")
"""Special enum containing all possible optimizers"""
@@ -1020,6 +1020,8 @@ def OPT(optName, *args, **kwargs) -> Optimizer:
from .pyALPSO.pyALPSO import ALPSO as opt
elif optName == "paropt" or optName == Optimizers.ParOpt:
from .pyParOpt.ParOpt import ParOpt as opt
+ elif optName == "egor" or optName == Optimizers.Egor:
+ from .pyEgor.pyEgor import Egor as opt
else:
raise ValueError(
(
diff --git a/pyoptsparse/testing/pyOpt_testing.py b/pyoptsparse/testing/pyOpt_testing.py
index 6c1ea6c57..b381b04a5 100644
--- a/pyoptsparse/testing/pyOpt_testing.py
+++ b/pyoptsparse/testing/pyOpt_testing.py
@@ -55,6 +55,7 @@ def get_dict_distance(d, d2):
"NLPQLP": {"iFile": ".out"},
"ParOpt": {"output_file": ".out", "tr_output_file": ".tr", "mma_output_file": ".mma"},
"ALPSO": {"filename": ".out"},
+ "Egor": {},
"NSGA2": {},
"Uno": {"logger_stream": ".out"},
}
diff --git a/pyproject.toml b/pyproject.toml
index b3723001f..d9b332a31 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,7 @@ dependencies = [
"sqlitedict>=1.6",
"numpy>=1.25",
"scipy>=1.11",
- "mdolab-baseclasses>=1.3.1"
+ "mdolab-baseclasses>=1.3.1",
]
dynamic = ["version"] # version is dynamically populated from meson project
@@ -30,6 +30,7 @@ docs = [
testing = [
"testflo>=1.4.5",
"parameterized",
+ "egobox>=0.37.6,<0.38.0",
]
dev = [
"meson-python",
diff --git a/tests/test_egor.py b/tests/test_egor.py
new file mode 100644
index 000000000..6a9803c19
--- /dev/null
+++ b/tests/test_egor.py
@@ -0,0 +1,173 @@
+"""Test class for Egor specific tests"""
+
+# Standard Python modules
+import json
+import tempfile
+
+# External modules
+import numpy as np
+
+# First party modules
+from pyoptsparse import Optimization
+from pyoptsparse.testing import OptTest
+
+
+class TestEgor(OptTest):
+ def setup_xsinx_optProb(self):
+ """
+ Setup the optimization problem for the xsinx function.
+
+ The xsinx function is defined as:
+ f(x) = (x - 3.5) * sin((x - 3.5) / π)
+
+ Domain: x ∈ [0, 25]
+ Solution opt pb: fStar ≈ -15.1 at x ≈ 18.935
+ """
+
+ def objfunc(xdict):
+ x = xdict["x"]
+ funcs = {}
+ # xsinx function: (x - 3.5) * sin((x - 3.5) / π)
+ funcs["obj"] = (x - 3.5) * np.sin((x - 3.5) / np.pi)
+ fail = False
+ return funcs, fail
+
+ optProb = Optimization("xsinx Function", objfunc)
+ optProb.addVar("x", lower=0.0, upper=25.0)
+ optProb.addObj("obj")
+ self.optName = "Egor"
+ self.optProb = optProb
+
+ def test_egor(self):
+ self.setup_xsinx_optProb()
+ sol = self.optimize()
+ # Check Solution
+ self.assertLess(sol.fStar, -15.0)
+
+ def test_egor_inform(self):
+ self.setup_xsinx_optProb()
+ # Test that the inform is "Maximum number of iterations reached"
+ sol = self.optimize(optOptions={"max_iters": 1})
+ self.assert_inform_equal(sol, 1)
+
+ # Test that the inform is "Target function value reached"
+ sol = self.optimize(optOptions={"target": -10.0})
+ self.assert_inform_equal(sol, 2)
+
+ # Test that the inform is "Time limit reached"
+ sol = self.optimize(optOptions={"timeout": 1e-6})
+ self.assert_inform_equal(sol, 5)
+
+ def test_egor_warm_start(self):
+ with tempfile.TemporaryDirectory() as outdir:
+ self.setup_xsinx_optProb()
+ # First run to generate a history file
+ sol1 = self.optimize(optOptions={"max_iters": 1, "outdir": outdir, "seed": 0})
+ # Second run with warm start
+ sol2 = self.optimize(optOptions={"max_iters": 5, "outdir": outdir, "warm_start": True})
+ # Check that the second run continued from the first run
+ self.assertGreater(sol1.fStar, sol2.fStar)
+
+ def test_egor_config(self):
+ with tempfile.TemporaryDirectory() as outdir:
+ self.setup_xsinx_optProb()
+ # Test that the gp_config option is passed correctly
+ gp_config = {"corr_spec": 4, "kpls_dim": 1}
+ _ = self.optimize(
+ optOptions={
+ "infill_strategy": 1,
+ "gp_config": gp_config,
+ "outdir": outdir,
+ "trego": {"n_gl_steps": (1, 3)},
+ }
+ )
+ # read egor_config.json from outdir and check that corr_spec is 4
+ with open(f"{outdir}/egor_config.json", "r") as f:
+ egor_config = json.load(f)
+ self.assertEqual(egor_config["gp"]["correlation_spec"], "MATERN32")
+ self.assertEqual(egor_config["gp"]["kpls_dim"], 1)
+ self.assertEqual(egor_config["infill_criterion"]["type_infill"], "ExpectedImprovement")
+ self.assertEqual(egor_config["iteration_strategy"]["type_iteration_strategy"], "TregoStrategy")
+ self.assertEqual(egor_config["iteration_strategy"]["n_gl_steps"], [1, 3])
+
+ def test_egor_ackley(self):
+ """
+ Test that Egor can optimize the Ackley function.
+ """
+
+ def objfunc(xdict):
+ x = xdict["xvars"]
+ funcs = {}
+ funcs["obj"] = (
+ -20.0 * np.exp(-0.2 * np.sqrt(0.5 * (x[0] ** 2 + x[1] ** 2)))
+ - np.exp(0.5 * (np.cos(2.0 * np.pi * x[0]) + np.cos(2.0 * np.pi * x[1])))
+ + np.e
+ + 20
+ )
+ fail = False
+ return funcs, fail
+
+ optProb = Optimization("Ackley Function", objfunc)
+ optProb.addVarGroup("xvars", 2, lower=[-32.768, -32.768], upper=[32.768, 32.768])
+ optProb.addObj("obj")
+ self.optName = "Egor"
+ self.optProb = optProb
+ sol = self.optimize(
+ optOptions={
+ "max_iters": 100,
+ "verbose": 2, # level of verbosity, 0 = error, 1 = warn, 2 = info, 3 = debug
+ "n_doe": 15,
+ "gp_config": {
+ "corr_spec": 8
+ }, # corr spec: 1 = absolute exponential, 2 = squared exponential, 4 = matern 3/2, 8 = matern 5/2
+ "seed": 0,
+ "trego": {"n_gl_steps": (1, 4)},
+ }
+ )
+ # Check Solution
+ self.fStar = [0.0]
+ self.xStar = [
+ {"xvars": (0.0, 0.0)},
+ ]
+ self.assert_solution_allclose(sol, tol=1e-2)
+
+ def test_egor_g24(self):
+ """
+ Test Egor on the G24 problem.
+
+ The G24 problem is defined as:
+ minimize f(x) = -x1 - x2
+ subject to:
+ c1(x) = -2*x1^4 + 8*x1^3 - 8*x1^2 + x2 - 2 <= 0
+ c2(x) = -4*x1^4 + 32*x1^3 - 88*x1^2 + 96*x1 + x2 - 36 <= 0
+ with x1 in [0, 3] and x2 in [0, 4]
+
+ Global optimum: x_opt = (2.3295, 3.1785), f_opt = -5.5080
+ """
+
+ def objfunc(xdict):
+ x = xdict["xvars"]
+ funcs = {}
+ funcs["obj"] = -x[0] - x[1]
+ funcs["con"] = [
+ -2.0 * x[0] ** 4 + 8.0 * x[0] ** 3 - 8.0 * x[0] ** 2 + x[1] - 2.0,
+ -4.0 * x[0] ** 4 + 32.0 * x[0] ** 3 - 88.0 * x[0] ** 2 + 96.0 * x[0] + x[1] - 36.0,
+ ]
+ fail = False
+ return funcs, fail
+
+ optProb = Optimization("G24 Function", objfunc)
+ optProb.addVarGroup("xvars", 2, lower=[0.0, 0.0], upper=[3.0, 4.0])
+ optProb.addObj("obj")
+ optProb.addConGroup("con", 2, upper=0.0)
+ self.optName = "Egor"
+ self.optProb = optProb
+ sol = self.optimize(
+ optOptions={"max_iters": 30, "n_doe": 5, "target": -5.50, "cstr_tol": [1e-3, 1e-3], "verbose": 2}
+ )
+ # Check Solution
+ self.fStar = [-5.5080]
+ self.xStar = [
+ {"xvars": (2.3295, 3.1785)},
+ ]
+ self.assert_solution_allclose(sol, tol=1e-2)
diff --git a/tests/test_hs015.py b/tests/test_hs015.py
index 4da28a180..9ac45f64e 100644
--- a/tests/test_hs015.py
+++ b/tests/test_hs015.py
@@ -47,8 +47,16 @@ class TestHS15(OptTest):
"IPOPT": 1e-4,
"CONMIN": 1e-10,
"PSQP": 5e-12,
+ "Uno": 1e-4,
+ "Egor": 5e-2,
+ }
+ optOptions = {
+ "Egor": {
+ "max_iters": 50,
+ "n_doe": 30,
+ "seed": 42,
+ }
}
- optOptions = {}
def objfunc(self, xdict):
self.nf += 1
@@ -116,11 +124,11 @@ def test_snopt(self):
# sol_xvars = [sol.variables["xvars"][i].value for i in range(2)]
# assert_allclose(sol_xvars, dv["xvars"], atol=tol, rtol=tol)
- @parameterized.expand(["SLSQP", "PSQP", "CONMIN", "NLPQLP"])
+ @parameterized.expand(["SLSQP", "PSQP", "CONMIN", "NLPQLP", "Egor"])
def test_optimization(self, optName):
self.optName = optName
self.setup_optProb()
- optOptions = self.optOptions.pop(optName, None)
+ optOptions = self.optOptions.get(optName, None)
sol = self.optimize(optOptions=optOptions)
# Check Solution
self.assert_solution_allclose(sol, self.tol[optName])
diff --git a/tests/test_hs071.py b/tests/test_hs071.py
index 1ca35b010..cb6b61b06 100644
--- a/tests/test_hs071.py
+++ b/tests/test_hs071.py
@@ -33,12 +33,14 @@ class TestHS71(OptTest):
"CONMIN": 1e-3,
"PSQP": 1e-6,
"Uno": 1e-4,
+ "Egor": 1e-2,
}
optOptions = {
"CONMIN": {
"DELFUN": 1e-10,
"DABFUN": 1e-10,
- }
+ },
+ "Egor": {"max_iters": 100, "seed": 42, "trego": {"n_gl_steps": (1, 4)}},
}
def objfunc(self, xdict):
@@ -257,7 +259,7 @@ def test_psqp_informs(self):
sol = self.optimize(optOptions={"MIT": 1})
self.assert_inform_equal(sol, 11)
- @parameterized.expand(["SNOPT", "IPOPT", "SLSQP", "PSQP", "CONMIN", "NLPQLP", "Uno"])
+ @parameterized.expand(["SNOPT", "IPOPT", "SLSQP", "PSQP", "CONMIN", "NLPQLP", "Uno", "Egor"])
def test_optimization(self, optName):
self.optName = optName
self.setup_optProb()
diff --git a/tests/test_sphere.py b/tests/test_sphere.py
index 2dc770142..dbe162435 100644
--- a/tests/test_sphere.py
+++ b/tests/test_sphere.py
@@ -41,7 +41,7 @@ class TestSphere(OptTest):
xStar = {"xvars": np.zeros(N)}
# Tolerances
- tol = {k: 5e-2 if k in ["CONMIN", "ALPSO", "NSGA2"] else 1e-6 for k in ALL_OPTIMIZERS}
+ tol = {k: 5e-2 if k in ["CONMIN", "ALPSO", "NSGA2", "Egor"] else 1e-6 for k in ALL_OPTIMIZERS}
optOptions = {
"ALPSO": { # sphere
@@ -61,6 +61,7 @@ class TestSphere(OptTest):
"Major iterations limit": 10,
},
"Uno": {"max_iterations": 100, "preset": "filtersqp"},
+ "Egor": {"max_iters": 100, "seed": 123, "trego": {"n_gl_steps": (1, 4)}},
}
def objfunc(self, xdict):