From 1ea0f41d0a34c521f0dc65b4a658176d5f809bfc Mon Sep 17 00:00:00 2001 From: relf Date: Tue, 12 Dec 2023 20:53:09 +0100 Subject: [PATCH 1/5] Bump 0.14 --- Cargo.toml | 10 +++++----- doe/Cargo.toml | 2 +- ego/Cargo.toml | 8 ++++---- gp/Cargo.toml | 4 ++-- moe/Cargo.toml | 6 +++--- pyproject.toml | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea85e1ca..0bfe9558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egobox" -version = "0.13.0" +version = "0.14.0" authors = ["Rémi Lafage "] edition = "2021" description = "A toolbox for efficient global optimization" @@ -31,10 +31,10 @@ persistent-ego = ["egobox-ego/persistent"] blas = ["ndarray/blas", "egobox-gp/blas", "egobox-moe/blas", "egobox-ego/blas"] [dependencies] -egobox-doe = { version = "0.13.0", path = "./doe" } -egobox-gp = { version = "0.13.0", path = "./gp" } -egobox-moe = { version = "0.13.0", path = "./moe", features = ["persistent"] } -egobox-ego = { version = "0.13.0", path = "./ego", features = ["persistent"] } +egobox-doe = { version = "0.14.0", path = "./doe" } +egobox-gp = { version = "0.14.0", path = "./gp" } +egobox-moe = { version = "0.14.0", path = "./moe", features = ["persistent"] } +egobox-ego = { version = "0.14.0", path = "./ego", features = ["persistent"] } linfa = { version = "0.7", default-features = false } diff --git a/doe/Cargo.toml b/doe/Cargo.toml index 07a00339..f5128580 100644 --- a/doe/Cargo.toml +++ b/doe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egobox-doe" -version = "0.13.0" +version = "0.14.0" authors = ["Rémi Lafage "] edition = "2021" description = "A library for design of experiments" diff --git a/ego/Cargo.toml b/ego/Cargo.toml index 123542d2..6962ec81 100644 --- a/ego/Cargo.toml +++ b/ego/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egobox-ego" -version = "0.13.0" +version = "0.14.0" authors = ["Rémi Lafage "] edition = "2021" description = "A library for efficient global optimization" @@ -16,11 +16,11 @@ persistent = ["serde_json"] blas = ["ndarray-linalg", "linfa/ndarray-linalg", "linfa-pls/blas"] [dependencies] -egobox-doe = { version = "0.13.0", path = "../doe", features = [ +egobox-doe = { version = "0.14.0", path = "../doe", features = [ "serializable", ] } -egobox-gp = { version = "0.13.0", path = "../gp", features = ["serializable"] } -egobox-moe = { version = "0.13.0", path = "../moe", features = [ +egobox-gp = { version = "0.14.0", path = "../gp", features = ["serializable"] } +egobox-moe = { version = "0.14.0", path = "../moe", features = [ "serializable", ] } diff --git a/gp/Cargo.toml b/gp/Cargo.toml index 4d87cf79..1d643dcf 100644 --- a/gp/Cargo.toml +++ b/gp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egobox-gp" -version = "0.13.0" +version = "0.14.0" authors = ["Rémi Lafage "] edition = "2021" description = "A library for gaussian process modeling" @@ -17,7 +17,7 @@ serializable = ["serde", "linfa/serde"] blas = ["ndarray-linalg", "linfa/ndarray-linalg", "linfa-pls/blas"] [dependencies] -egobox-doe = { version = "0.13.0", path = "../doe" } +egobox-doe = { version = "0.14.0", path = "../doe" } linfa = { version = "0.7", default-features = false } linfa-pls = { version = "0.7", default-features = false } diff --git a/moe/Cargo.toml b/moe/Cargo.toml index 423f7f82..1a9aea82 100644 --- a/moe/Cargo.toml +++ b/moe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egobox-moe" -version = "0.13.0" +version = "0.14.0" authors = ["Rémi Lafage "] edition = "2021" description = "A library for mixture of expert gaussian processes" @@ -29,8 +29,8 @@ serializable = [ blas = ["ndarray-linalg", "linfa/ndarray-linalg", "linfa-pls/blas"] [dependencies] -egobox-doe = { version = "0.13.0", path = "../doe" } -egobox-gp = { version = "0.13.0", path = "../gp" } +egobox-doe = { version = "0.14.0", path = "../doe" } +egobox-gp = { version = "0.14.0", path = "../gp" } linfa = { version = "0.7", default-features = false } linfa-clustering = { version = "0.7", default-features = false } diff --git a/pyproject.toml b/pyproject.toml index d2309ea7..7960c272 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ python-source = "python" [tool.poetry] name = "egobox" -version = "0.13.0" +version = "0.14.0" description = "Python binding for egobox EGO optimizer written in Rust" authors = ["Rémi Lafage "] packages = [{ include = "egobox", from = "python" }] From 82ad000fab7eda4017eeb1f515af68f27b674762 Mon Sep 17 00:00:00 2001 From: relf Date: Tue, 12 Dec 2023 21:46:34 +0100 Subject: [PATCH 2/5] Update readme and changelog --- CHANGELOG.md | 8 +++++++- README.md | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc60d77..56942da5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ Changes ------- -Version 0.14.0 - unreleased +Version 0.15.0 - unreleased =========================== +Version 0.14.0 - 13/12/2023 +=========================== + +* `ego`: Fix ask-and-tell interface suggest method in presence of discrete variable +A few breaking changes in the API of `Egor`, `EgorService`, `EgorSolver` and `EgorConfig` + Version 0.13.0 - 30/11/2023 =========================== diff --git a/README.md b/README.md index 72906086..51333b1e 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ Depending on the sub-packages you want to use, you have to add following declara ```text [dependencies] -egobox-doe = { version = "0.11.0" } -egobox-gp = { version = "0.11.0" } -egobox-moe = { version = "0.11.0" } -egobox-ego = { version = "0.11.0" } +egobox-doe = { version = "0.14.0" } +egobox-gp = { version = "0.14.0" } +egobox-moe = { version = "0.14.0" } +egobox-ego = { version = "0.14.0" } ``` ### Features @@ -86,7 +86,7 @@ Otherwise, you can choose an external BLAS/LAPACK backend available through the Thus, for instance, to use `gp` with the Intel MKL BLAS/LAPACK backend, you could specify in your `Cargo.toml` the following features: ```text [dependencies] -egobox-gp = { version = "0.11.0", features = ["blas", "linfa/intel-mkl-static"] } +egobox-gp = { version = "0.14.0", features = ["blas", "linfa/intel-mkl-static"] } ``` or you could run the `gp` example as follows: ``` bash From 5f7c88f925e32b6703783b471dfb2fc6bba7fe0c Mon Sep 17 00:00:00 2001 From: relf Date: Wed, 13 Dec 2023 09:21:55 +0100 Subject: [PATCH 3/5] Update changelog --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56942da5..6bfd4cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ Version 0.15.0 - unreleased Version 0.14.0 - 13/12/2023 =========================== -* `ego`: Fix ask-and-tell interface suggest method in presence of discrete variable -A few breaking changes in the API of `Egor`, `EgorService`, `EgorSolver` and `EgorConfig` +* `ego`: Fix ask-and-tell interface `suggest()` method in presence of discrete variable to work +in discrete not in continuous space +A few API breaking changes: +* `EgorConfig::xtypes` not an option anymore +* `EgorSolver::new_with_xtypes()` renamed `new` as `new` with xlimits is removed, use `to_xtypes` to convert `xlimits` +* `EgorConfig::no_discrete` attribute removed, use `EgorConfig::discrete()` method +* `SurrogateBuilder::new_with_xtypes_rng` renamed `new_with_xtypes` Version 0.13.0 - 30/11/2023 =========================== From 5a7a14c758c37aea123fa7b6e434711d95670456 Mon Sep 17 00:00:00 2001 From: relf Date: Wed, 13 Dec 2023 10:01:45 +0100 Subject: [PATCH 4/5] Add to_discrete|continuous_space aliases --- ego/src/egor.rs | 6 ++---- ego/src/egor_service.rs | 5 ++--- ego/src/egor_solver.rs | 9 ++++----- ego/src/mixint.rs | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ego/src/egor.rs b/ego/src/egor.rs index 293dd417..45ee732f 100644 --- a/ego/src/egor.rs +++ b/ego/src/egor.rs @@ -212,8 +212,7 @@ impl Egor { y_hist: y_data, } } else { - let x_data = cast_to_discrete_values(&xtypes, &x_data); - let x_data = fold_with_enum_index(&xtypes, &x_data.view()); + let x_data = to_discrete_space(&xtypes, &x_data.view()); info!("History: \n{}", concatenate![Axis(1), x_data, y_data]); let x_opt = result @@ -222,8 +221,7 @@ impl Egor { .unwrap() .to_owned() .insert_axis(Axis(0)); - let x_opt = cast_to_discrete_values(&xtypes, &x_opt); - let x_opt = fold_with_enum_index(&xtypes, &x_opt.view()); + let x_opt = to_discrete_space(&xtypes, &x_opt.view()); OptimResult { x_opt: x_opt.row(0).to_owned(), y_opt: result.state.get_full_best_cost().unwrap().to_owned(), diff --git a/ego/src/egor_service.rs b/ego/src/egor_service.rs index fa2b143f..8e58bb49 100644 --- a/ego/src/egor_service.rs +++ b/ego/src/egor_service.rs @@ -131,10 +131,9 @@ impl EgorService { y_data: &ArrayBase, Ix2>, ) -> Array2 { let xtypes = &self.solver.config.xtypes; - let x_data = unfold_with_enum_mask(xtypes, x_data); + let x_data = to_continuous_space(xtypes, x_data); let x = self.solver.suggest(&x_data, y_data); - let x = cast_to_discrete_values(xtypes, &x); - fold_with_enum_index(xtypes, &x).to_owned() + to_discrete_space(xtypes, &x).to_owned() } } diff --git a/ego/src/egor_solver.rs b/ego/src/egor_solver.rs index f130108d..e9f9e7cb 100644 --- a/ego/src/egor_solver.rs +++ b/ego/src/egor_solver.rs @@ -312,7 +312,7 @@ where let doe = hstart_doe.as_ref().or(self.config.doe.as_ref()); let (y_data, x_data) = if let Some(doe) = doe { - let doe = unfold_with_enum_mask(&self.config.xtypes, doe); + let doe = to_continuous_space(&self.config.xtypes, doe); if doe.ncols() == self.xlimits.nrows() { // only x are specified @@ -970,10 +970,9 @@ where x: &Array2, ) -> Array2 { let params = if self.config.discrete() { - // When xtypes is specified, we have to cast x to folded space - // as EgorSolver works internally in the continuous space - let xcast = cast_to_discrete_values(&self.config.xtypes, x); - fold_with_enum_index(&self.config.xtypes, &xcast.view()) + // We have to cast x to folded space as EgorSolver + // works internally in the continuous space + to_discrete_space(&self.config.xtypes, x) } else { x.to_owned() }; diff --git a/ego/src/mixint.rs b/ego/src/mixint.rs index a17b53a5..c3e8fd22 100644 --- a/ego/src/mixint.rs +++ b/ego/src/mixint.rs @@ -138,6 +138,15 @@ pub fn unfold_with_enum_mask( xunfold } +/// COntinuous relaxation of x given possibly discrete types +/// Alias of `unfold_with_enum_mask` +pub fn to_continuous_space( + xtypes: &[XType], + x: &ArrayBase, Ix2>, +) -> Array2 { + unfold_with_enum_mask(xtypes, x) +} + /// Find closest value to `val` in given slice `v`. fn take_closest(v: &[F], val: F) -> F { let idx = Array::from_vec(v.to_vec()) @@ -202,6 +211,15 @@ pub fn cast_to_discrete_values( xcast } +/// Convenient method to pass from continuous unfolded space to discrete folded space +pub fn to_discrete_space( + xtypes: &[XType], + x: &ArrayBase, Ix2>, +) -> Array2 { + let x = cast_to_discrete_values(xtypes, x); + fold_with_enum_index(xtypes, &x) +} + enum Method { Lhs, FullFactorial, From 0f50de49411146f41ffca81752feee05ce89ea95 Mon Sep 17 00:00:00 2001 From: relf Date: Wed, 13 Dec 2023 10:49:26 +0100 Subject: [PATCH 5/5] Better names in mixint --- ego/src/criteria/ei.rs | 1 + ego/src/criteria/wb2.rs | 4 +++- ego/src/egor_solver.rs | 2 +- ego/src/egor_state.rs | 2 +- ego/src/mixint.rs | 40 +++++++++++++++++----------------------- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/ego/src/criteria/ei.rs b/ego/src/criteria/ei.rs index 9570cdb7..4060d245 100644 --- a/ego/src/criteria/ei.rs +++ b/ego/src/criteria/ei.rs @@ -91,4 +91,5 @@ impl InfillCriterion for ExpectedImprovement { } } +/// Expected Improvement infill criterion pub const EI: ExpectedImprovement = ExpectedImprovement {}; diff --git a/ego/src/criteria/wb2.rs b/ego/src/criteria/wb2.rs index 1f264460..b7669466 100644 --- a/ego/src/criteria/wb2.rs +++ b/ego/src/criteria/wb2.rs @@ -56,7 +56,7 @@ impl InfillCriterion for WB2Criterion { } /// Computes the scaling factor used to scale WB2 infill criteria. -pub fn compute_wb2s_scale( +pub(crate) fn compute_wb2s_scale( x: &ArrayView2, obj_model: &dyn ClusteredSurrogate, f_min: f64, @@ -78,7 +78,9 @@ pub fn compute_wb2s_scale( } } +/// WB2 infill criterion pub const WB2: WB2Criterion = WB2Criterion(Some(1.0)); +/// WB2 scaled infill criterion pub const WB2S: WB2Criterion = WB2Criterion(None); #[cfg(test)] diff --git a/ego/src/egor_solver.rs b/ego/src/egor_solver.rs index e9f9e7cb..6338a603 100644 --- a/ego/src/egor_solver.rs +++ b/ego/src/egor_solver.rs @@ -230,7 +230,7 @@ impl EgorSolver { let xtypes = config.xtypes.clone(); EgorSolver { config, - xlimits: unfold_xtypes_as_continuous_limits(&xtypes), + xlimits: as_continuous_limits(&xtypes), surrogate_builder: SB::new_with_xtypes(&xtypes), rng, } diff --git a/ego/src/egor_state.rs b/ego/src/egor_state.rs index ec95cee5..255054e9 100644 --- a/ego/src/egor_state.rs +++ b/ego/src/egor_state.rs @@ -13,7 +13,7 @@ use std::{collections::HashMap, iter::zip}; /// Max number of retry when adding a new point. Point addition may fail /// if new point is too close to a previous point in the growing doe used /// to train surrogate models modeling objective and constraints functions. -pub const MAX_POINT_ADDITION_RETRY: i32 = 3; +pub(crate) const MAX_POINT_ADDITION_RETRY: i32 = 3; /// Find best (eg minimal) cost value (y_data\[0\]) with valid constraints (y_data\[1..\] < cstr_tol). /// y_data containing ns samples [objective, cstr_1, ... cstr_nc] is given as a matrix (ns, nc + 1) diff --git a/ego/src/mixint.rs b/ego/src/mixint.rs index c3e8fd22..8793bd5d 100644 --- a/ego/src/mixint.rs +++ b/ego/src/mixint.rs @@ -30,7 +30,7 @@ use std::marker::PhantomData; /// /// Each level of an enumerate gives a new continuous dimension in [0, 1]. /// Each integer dimensions are relaxed continuously. -pub fn unfold_xtypes_as_continuous_limits(xtypes: &[XType]) -> Array2 { +pub fn as_continuous_limits(xtypes: &[XType]) -> Array2 { let mut xlimits: Vec = vec![]; let mut dim = 0; xtypes.iter().for_each(|xtype| match xtype { @@ -69,7 +69,7 @@ pub fn unfold_xtypes_as_continuous_limits(xtypes: &[XType]) -> Array2< /// the input x may contain the mask [..., 0, 0, 1, ...] which will be contracted in [..., 2, ...] /// meaning the "green" value. /// This function is the opposite of unfold_with_enum_mask(). -pub fn fold_with_enum_index( +pub(crate) fn fold_with_enum_index( xtypes: &[XType], x: &ArrayBase, Ix2>, ) -> Array2 { @@ -91,7 +91,7 @@ pub fn fold_with_enum_index( } /// Compute dimension when all variables are continuously relaxed -fn compute_unfolded_dimension(xtypes: &[XType]) -> usize { +fn compute_continuous_dim(xtypes: &[XType]) -> usize { xtypes .iter() .map(|s| match s { @@ -108,11 +108,11 @@ fn compute_unfolded_dimension(xtypes: &[XType]) -> usize { /// For instance, if an input dimension is typed ["blue", "red", "green"] a sample/row of /// the input x may contain [..., 2, ...] which will be expanded in [..., 0, 0, 1, ...]. /// This function is the opposite of fold_with_enum_index(). -pub fn unfold_with_enum_mask( +pub(crate) fn unfold_with_enum_mask( xtypes: &[XType], x: &ArrayBase, Ix2>, ) -> Array2 { - let mut xunfold = Array::zeros((x.nrows(), compute_unfolded_dimension(xtypes))); + let mut xunfold = Array::zeros((x.nrows(), compute_continuous_dim(xtypes))); let mut unfold_index = 0; xtypes.iter().enumerate().for_each(|(i, s)| match s { XType::Cont(_, _) | XType::Int(_, _) | XType::Ord(_) => { @@ -138,7 +138,7 @@ pub fn unfold_with_enum_mask( xunfold } -/// COntinuous relaxation of x given possibly discrete types +/// Continuous relaxation of x given possibly discrete types /// Alias of `unfold_with_enum_mask` pub fn to_continuous_space( xtypes: &[XType], @@ -202,7 +202,7 @@ fn cast_to_discrete_values_mut( /// For instance, if an input dimension is typed ["blue", "red", "green"] in xlimits a sample/row of /// the input x may contain the values (or mask) [..., 0, 0, 1, ...] to specify "green" for /// this original dimension. -pub fn cast_to_discrete_values( +pub(crate) fn cast_to_discrete_values( xtypes: &[XType], x: &ArrayBase, Ix2>, ) -> Array2 { @@ -659,7 +659,7 @@ impl MixintContext { /// Compute input dim once unfolded due to continupous relaxation pub fn get_unfolded_dim(&self) -> usize { - compute_unfolded_dimension(&self.xtypes) + compute_continuous_dim(&self.xtypes) } /// Create a mixed integer LHS @@ -667,13 +667,10 @@ impl MixintContext { &self, seed: Option, ) -> MixintSampling> { - let lhs = seed.map_or( - Lhs::new(&unfold_xtypes_as_continuous_limits(&self.xtypes)), - |seed| { - let rng = Xoshiro256Plus::seed_from_u64(seed); - Lhs::new(&unfold_xtypes_as_continuous_limits(&self.xtypes)).with_rng(rng) - }, - ); + let lhs = seed.map_or(Lhs::new(&as_continuous_limits(&self.xtypes)), |seed| { + let rng = Xoshiro256Plus::seed_from_u64(seed); + Lhs::new(&as_continuous_limits(&self.xtypes)).with_rng(rng) + }); MixintSampling { method: lhs, xtypes: self.xtypes.clone(), @@ -685,7 +682,7 @@ impl MixintContext { /// Create a mixed integer full factorial pub fn create_ffact_sampling(&self) -> MixintSampling> { MixintSampling { - method: FullFactorial::new(&unfold_xtypes_as_continuous_limits(&self.xtypes)), + method: FullFactorial::new(&as_continuous_limits(&self.xtypes)), xtypes: self.xtypes.clone(), output_in_folded_space: self.work_in_folded_space, phantom: PhantomData, @@ -697,13 +694,10 @@ impl MixintContext { &self, seed: Option, ) -> MixintSampling> { - let rand = seed.map_or( - Random::new(&unfold_xtypes_as_continuous_limits(&self.xtypes)), - |seed| { - let rng = Xoshiro256Plus::seed_from_u64(seed); - Random::new(&unfold_xtypes_as_continuous_limits(&self.xtypes)).with_rng(rng) - }, - ); + let rand = seed.map_or(Random::new(&as_continuous_limits(&self.xtypes)), |seed| { + let rng = Xoshiro256Plus::seed_from_u64(seed); + Random::new(&as_continuous_limits(&self.xtypes)).with_rng(rng) + }); MixintSampling { method: rand, xtypes: self.xtypes.clone(),