diff --git a/ego/src/egor.rs b/ego/src/egor.rs index 178fcf81..a5f94254 100644 --- a/ego/src/egor.rs +++ b/ego/src/egor.rs @@ -168,7 +168,7 @@ impl EgorBuilder { }; Egor { fobj: ObjFunc::new(self.fobj), - solver: EgorSolver::new_with_xtypes(xtypes, rng), + solver: EgorSolver::new_with_xtypes(self.config, xtypes, rng), } } } diff --git a/ego/src/egor_service.rs b/ego/src/egor_service.rs index 388177a1..acdda956 100644 --- a/ego/src/egor_service.rs +++ b/ego/src/egor_service.rs @@ -1,34 +1,42 @@ -//! Egor optimizer service implements Egor optimizer with an ask-and-tell interface. +//! Egor optimizer service implements [`Egor`] optimizer with an ask-and-tell interface. +//! It allows to keep the control on the iteration loop by asking for optimum location +//! suggestions and telling objective function values at these points. //! //! ```no_run -//! # use ndarray::{array, Array2, ArrayView1, ArrayView2, Zip}; +//! # use ndarray::{array, Array2, ArrayView1, ArrayView2, Zip, concatenate, Axis}; //! # use egobox_doe::{Lhs, SamplingMethod}; -//! # use egobox_ego::{EgorBuilder, InfillStrategy, InfillOptimizer}; +//! # use egobox_ego::{EgorServiceBuilder, InfillStrategy, RegressionSpec, CorrelationSpec}; //! //! # use rand_xoshiro::Xoshiro256Plus; //! # use ndarray_rand::rand::SeedableRng; //! use argmin_testfunctions::rosenbrock; //! -//! // Rosenbrock test function: minimum y_opt = 0 at x_opt = (1, 1) -//! fn rosenb(x: &ArrayView2) -> Array2 { -//! let mut y: Array2 = Array2::zeros((x.nrows(), 1)); -//! Zip::from(y.rows_mut()) -//! .and(x.rows()) -//! .par_for_each(|mut yi, xi| yi.assign(&array![rosenbrock(&xi.to_vec(), 1., 100.)])); -//! y +//! fn xsinx(x: &ArrayView2) -> Array2 { +//! (x - 3.5) * ((x - 3.5) / std::f64::consts::PI).mapv(|v| v.sin()) //! } //! -//! let xlimits = array![[-2., 2.], [-2., 2.]]; -//! // TODO -//! //let res = EgorBuilder::optimize(rosenb) -//! // .min_within(&xlimits) -//! // .infill_strategy(InfillStrategy::EI) -//! // .n_doe(10) -//! // .target(1e-1) -//! // .n_iter(30) -//! // .run() -//! // .expect("Rosenbrock minimization"); -//! //println!("Rosenbrock min result = {:?}", res); +//! let egor = EgorServiceBuilder::optimize() +//! .configure(|conf| { +//! conf.regression_spec(RegressionSpec::ALL) +//! .correlation_spec(CorrelationSpec::ALL) +//! .infill_strategy(InfillStrategy::EI) +//! }) +//! .random_seed(42) +//! .min_within(&array![[0., 25.]]); +//! +//! let mut doe = array![[0.], [7.], [20.], [25.]]; +//! let mut y_doe = xsinx(&doe.view()); +//! +//! for _i in 0..10 { +//! // we tell function values and ask for next suggested optimum location +//! let x_suggested = egor.suggest(&doe, &y_doe); +//! +//! // we update the doe +//! doe = concatenate![Axis(0), doe, x_suggested]; +//! y_doe = xsinx(&doe.view()); +//! } +//! +//! println!("Rosenbrock min result = {:?}", doe); //! ``` //! use crate::egor_config::*; @@ -42,7 +50,7 @@ use ndarray_rand::rand::SeedableRng; use rand_xoshiro::Xoshiro256Plus; /// EGO optimizer service builder allowing to use Egor optimizer -/// with an ask-and-tell interface. +/// as a service. /// pub struct EgorServiceBuilder { config: EgorConfig, @@ -104,23 +112,20 @@ impl EgorServiceBuilder { }; EgorService { config: self.config.clone(), - solver: EgorSolver::new_with_xtypes(xtypes, rng), + solver: EgorSolver::new_with_xtypes(self.config, xtypes, rng), } } } -/// Egor optimizer structure used to parameterize the underlying `argmin::Solver` -/// and trigger the optimization using `argmin::Executor`. +/// Egor optimizer service. #[derive(Clone)] pub struct EgorService { - #[allow(dead_code)] config: EgorConfig, solver: EgorSolver, } impl EgorService { - #[allow(dead_code)] - fn configure EgorConfig>(mut self, init: F) -> Self { + pub fn configure EgorConfig>(mut self, init: F) -> Self { self.config = init(self.config); self } diff --git a/ego/src/egor_solver.rs b/ego/src/egor_solver.rs index 8c355490..8fcfcff6 100644 --- a/ego/src/egor_solver.rs +++ b/ego/src/egor_solver.rs @@ -8,7 +8,7 @@ //! ```no_run //! use ndarray::{array, Array2, ArrayView1, ArrayView2, Zip}; //! use egobox_doe::{Lhs, SamplingMethod}; -//! use egobox_ego::{EgorBuilder, InfillStrategy, InfillOptimizer, ObjFunc, EgorSolver}; +//! use egobox_ego::{EgorBuilder, EgorConfig, InfillStrategy, InfillOptimizer, ObjFunc, EgorSolver}; //! use egobox_moe::MoeParams; //! use rand_xoshiro::Xoshiro256Plus; //! use ndarray_rand::rand::SeedableRng; @@ -27,7 +27,8 @@ //! let rng = Xoshiro256Plus::seed_from_u64(42); //! let xlimits = array![[-2., 2.], [-2., 2.]]; //! let fobj = ObjFunc::new(rosenb); -//! let solver: EgorSolver> = EgorSolver::new(&xlimits, rng); +//! let config = EgorConfig::default(); +//! let solver: EgorSolver> = EgorSolver::new(config, &xlimits, rng); //! let res = Executor::new(fobj, solver) //! .configure(|state| state.max_iters(20)) //! .run() @@ -46,7 +47,7 @@ //! ```no_run //! use ndarray::{array, Array2, ArrayView1, ArrayView2, Zip}; //! use egobox_doe::{Lhs, SamplingMethod}; -//! use egobox_ego::{EgorBuilder, InfillStrategy, InfillOptimizer, ObjFunc, EgorSolver}; +//! use egobox_ego::{EgorBuilder, EgorConfig, InfillStrategy, InfillOptimizer, ObjFunc, EgorSolver}; //! use egobox_moe::MoeParams; //! use rand_xoshiro::Xoshiro256Plus; //! use ndarray_rand::rand::SeedableRng; @@ -84,14 +85,17 @@ //! let doe = Lhs::new(&xlimits).sample(10); //! //! let fobj = ObjFunc::new(f_g24); -//! let solver: EgorSolver> = -//! EgorSolver::new(&xlimits, rng) +//! +//! let config = EgorConfig::default() //! .n_cstr(2) //! .infill_strategy(InfillStrategy::EI) //! .infill_optimizer(InfillOptimizer::Cobyla) //! .doe(&doe) //! .target(-5.5080); //! +//! let solver: EgorSolver> = +//! EgorSolver::new(config, &xlimits, rng); +//! //! let res = Executor::new(fobj, solver) //! .configure(|state| state.max_iters(40)) //! .run() @@ -238,7 +242,7 @@ impl EgorSolver { /// /// The function `f` should return an objective but also constraint values if any. /// Design space is specified by a list of types for input variables `x` of `f` (see [`XType`]). - pub fn new_with_xtypes(xtypes: &[XType], rng: Xoshiro256Plus) -> Self { + pub fn new_with_xtypes(config: EgorConfig, xtypes: &[XType], rng: Xoshiro256Plus) -> Self { let env = Env::new().filter_or("EGOBOX_LOG", "info"); let mut builder = Builder::from_env(env); let builder = builder.target(env_logger::Target::Stdout); @@ -248,7 +252,7 @@ impl EgorSolver { config: EgorConfig { xtypes: Some(v_xtypes), no_discrete: no_discrete(xtypes), - ..EgorConfig::default() + ..config }, xlimits: unfold_xtypes_as_continuous_limits(xtypes), surrogate_builder: SB::new_with_xtypes_rng(xtypes), @@ -653,7 +657,6 @@ where lhs_optim, ) { Ok(xk) => { - println!(">>>>>>>>>> {}", self.config.n_cstr); match self.get_virtual_point(&xk, y_data, obj_model.as_ref(), cstr_models) { Ok(yk) => { y_dat = concatenate![ diff --git a/ego/src/lib.rs b/ego/src/lib.rs index 8ba7b5f3..8e8c5589 100644 --- a/ego/src/lib.rs +++ b/ego/src/lib.rs @@ -28,8 +28,8 @@ //! //! // We ask for 10 evaluations of the objective function to get the result //! let res = EgorBuilder::optimize(xsinx) +//! .configure(|config| config.n_iter(10)) //! .min_within(&array![[0.0, 25.0]]) -//! .n_iter(10) //! .run() //! .expect("xsinx minimized"); //! println!("Minimum found f(x) = {:?} at x = {:?}", res.x_opt, res.y_opt); @@ -100,11 +100,9 @@ //! approximating your objective function. //! //! ```no_run -//! # use egobox_ego::{EgorBuilder}; -//! # use ndarray::{array, Array2, ArrayView2}; -//! # fn fobj(x: &ArrayView2) -> Array2 { x.to_owned() } -//! # let egor = EgorBuilder::optimize(fobj).min_within(&array![[-1., 1.]]); -//! egor.n_doe(100); +//! # use egobox_ego::{EgorConfig}; +//! # let egor_config = EgorConfig::default(); +//! egor_config.n_doe(100); //! ``` //! //! You can also provide your initial doe though the `egor.doe(your_doe)` method. @@ -114,11 +112,8 @@ //! Gaussian process will be built using the `ndim` (usually 3 or 4) main components in the PLS projected space. //! //! ```no_run -//! # use egobox_ego::{EgorBuilder}; -//! # use ndarray::{array, Array2, ArrayView2}; -//! # fn fobj(x: &ArrayView2) -> Array2 { x.to_owned() } -//! # let egor = EgorBuilder::optimize(fobj).min_within(&array![[-1., 1.]]); -//! egor.kpls_dim(3); +//! # let egor_config = egobox_ego::EgorConfig::default(); +//! egor_config.kpls_dim(3); //! ``` //! //! * Specifications of constraints (expected to be negative at the end of the optimization) @@ -126,11 +121,8 @@ //! the objective function is expected to return an array '\[nsamples, 1 obj value + 2 const values\]'. //! //! ```no_run -//! # use egobox_ego::{EgorBuilder}; -//! # use ndarray::{array, Array2, ArrayView2}; -//! # fn fobj(x: &ArrayView2) -> Array2 { x.to_owned() } -//! # let egor = EgorBuilder::optimize(fobj).min_within(&array![[-1., 1.]]); -//! egor.n_cstr(2); +//! # let egor_config = egobox_ego::EgorConfig::default(); +//! egor_config.n_cstr(2); //! ``` //! //! * If the default infill strategy (WB2, Watson and Barnes 2nd criterion), @@ -138,11 +130,9 @@ //! See \[[Priem2019](#Priem2019)\] //! //! ```no_run -//! # use egobox_ego::{EgorBuilder, InfillStrategy}; -//! # use ndarray::{array, Array2, ArrayView2}; -//! # fn fobj(x: &ArrayView2) -> Array2 { x.to_owned() } -//! # let egor = EgorBuilder::optimize(fobj).min_within(&array![[-1., 1.]]); -//! egor.infill_strategy(InfillStrategy::EI); +//! # use egobox_ego::{EgorConfig, InfillStrategy}; +//! # let egor_config = EgorConfig::default(); +//! egor_config.infill_strategy(InfillStrategy::EI); //! ``` //! //! * The default gaussian process surrogate is parameterized with a constant trend and a squared exponential correlation kernel, also @@ -151,12 +141,10 @@ //! approximation (quality tested through cross validation). //! //! ```no_run -//! # use egobox_ego::{EgorBuilder, RegressionSpec, CorrelationSpec}; -//! # use ndarray::{array, Array2, ArrayView2}; -//! # fn fobj(x: &ArrayView2) -> Array2 { x.to_owned() } -//! # let egor = EgorBuilder::optimize(fobj).min_within(&array![[-1., 1.]]); -//! egor.regression_spec(RegressionSpec::CONSTANT | RegressionSpec::LINEAR) -//! .correlation_spec(CorrelationSpec::MATERN32 | CorrelationSpec::MATERN52); +//! # use egobox_ego::{EgorConfig, RegressionSpec, CorrelationSpec}; +//! # let egor_config = EgorConfig::default(); +//! egor_config.regression_spec(RegressionSpec::CONSTANT | RegressionSpec::LINEAR) +//! .correlation_spec(CorrelationSpec::MATERN32 | CorrelationSpec::MATERN52); //! ``` //! In the above example all GP with combinations of regression and correlation will be tested and the best combination for //! each modeled function will be retained. You can also simply specify `RegressionSpec::ALL` and `CorrelationSpec::ALL` to