Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python bindings for all LHS flavours #135

Merged
merged 5 commits into from
Feb 6, 2024
Merged
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
64 changes: 34 additions & 30 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,45 @@ 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`
* `SurrogateBuilder::new_with_xtypes_rng` renamed `new_with_xtypes`

Version 0.13.0 - 30/11/2023
===========================

* `ego`: API refactoring to enable `ask-and-tell` interface
* Configuration of Egor is factorize out in `EgorConfig`
* `EgorBuilder` gets a `configure` method to tune the configuration
* `EgorService` structure represent `Egor` when used as service
* `EgorService` structure represent `Egor` when used as service
* Python `Egor` API changes:
* function under optimization is now given via `minimize(fun, max_iters=...)` method
* new method `suggest(xdoe, ydoe)` allows to ask for x suggestion and tell current function evaluations
* new method `suggest(xdoe, ydoe)` allows to ask for x suggestion and tell current function evaluations
* new method `get_result(xdoe, ydoe)` to get the best evaluation (ie minimum) from given ones

Version 0.12.0 - 10/11/2023
===========================

* `gp` uses pure Rust COBYLA by @relf in https://github.com/relf/egobox/pull/110,https://github.com/relf/egobox/pull/113
* `ego` as pure Rust implementation (`nlopt` is now optional) by @relf in https://github.com/relf/egobox/pull/112
* `egobox` Python module: Simplify mixed-integer type declaration by @relf in https://github.com/relf/egobox/pull/115
* Upgrade dependencies by @relf in https://github.com/relf/egobox/pull/114
* Upgrade edition 2021 by @relf in https://github.com/relf/egobox/pull/109
* CI maintainance by @relf in https://github.com/relf/egobox/pull/111
* Bump actions/checkout from 2 to 4 by @dependabot in https://github.com/relf/egobox/pull/107
* Bump actions/setup-python from 2 to 4 by @dependabot in https://github.com/relf/egobox/pull/108
* `gp` uses pure Rust COBYLA by @relf in <https://github.com/relf/egobox/pull/110,https://github.com/relf/egobox/pull/113>
* `ego` as pure Rust implementation (`nlopt` is now optional) by @relf in <https://github.com/relf/egobox/pull/112>
* `egobox` Python module: Simplify mixed-integer type declaration by @relf in <https://github.com/relf/egobox/pull/115>
* Upgrade dependencies by @relf in <https://github.com/relf/egobox/pull/114>
* Upgrade edition 2021 by @relf in <https://github.com/relf/egobox/pull/109>
* CI maintainance by @relf in <https://github.com/relf/egobox/pull/111>
* Bump actions/checkout from 2 to 4 by @dependabot in <https://github.com/relf/egobox/pull/107>
* Bump actions/setup-python from 2 to 4 by @dependabot in <https://github.com/relf/egobox/pull/108>

Version 0.11.0 - 20/09/2023
===========================

* Automate Python package build and upload on Pypi from Github CI by @relf in https://github.com/relf/egobox/pull/104
* Fix FullFactorial when asked nb iof samples is small wrt x dimension by @relf in https://github.com/relf/egobox/pull/105
* Make mixed-integer sampling methods available in Python by @relf in https://github.com/relf/egobox/pull/106
* Automate Python package build and upload on Pypi from Github CI by @relf in <https://github.com/relf/egobox/pull/104>
* Fix FullFactorial when asked nb iof samples is small wrt x dimension
by @relf in <https://github.com/relf/egobox/pull/105>
* Make mixed-integer sampling methods available in Python by @relf in <https://github.com/relf/egobox/pull/106>

Version 0.10.0 - 22/06/2023
===========================

* `gp`, `moe` and `egobox` Python module:
* Added Gaussian process sampling (#97)
* Added Gaussian process sampling (#97)
* Added string representation (#98)

* `egobox` Python module:
Expand All @@ -72,18 +73,19 @@ Version 0.9.0 - 02/06/2023
==========================

* `ego`:
* Infill criterion is now a trait object in `EgorSolver` structure (#92)
* `Egor` and `EgorSolver` API: methods taking argument of type Option<T> now take argument of type T (#94)
* `EgorBuilder::min_within_mixed_space()` is now `EgorBuilder::min_within_mixint_space()` (#96)
* `egobox-ego` library doc updated (#95)
* Infill criterion is now a trait object in `EgorSolver` structure (#92)
* `Egor` and `EgorSolver` API: methods taking argument of type Option\<T\>
now take argument of type T (#94)
* `EgorBuilder::min_within_mixed_space()` is now `EgorBuilder::min_within_mixint_space()` (#96)
* `egobox-ego` library doc updated (#95)

* `egobox` Python module: Upgrade to PyO3 0.18 (#91)

Version 0.8.2 - 31/03/2023
==========================

* `ego`:
* Fix Egor solver best iter computation (#89)
* Fix Egor solver best iter computation (#89)

Version 0.8.1 - 28/03/2023
==========================
Expand Down Expand Up @@ -115,15 +117,15 @@ Version 0.8.0 - 10/03/2023
Version 0.7.0 - 11/01/2023
==========================

* `gp`:
* `gp`:
* Add analytic derivatives computations (#54, #55, #56, #58, #60). All derivatives available for all mean/correlation models are implemented.
* Refactor `MeanModel` and `CorrelationModel` methods:
* `apply()` renamed to `value()`
* `jac()` renamed to `jacobian()`
* Fix prediction computation when using linear regression (#52)
* `ego`:
* Refactor `Egor` using [`argmin 0.7.0` solver framework](http://argmin-rs.org) `EgorSolver` can be used with `argmin::Executor` and benefit from observers and checkpointing features (#67)
* `Egor` use kriging setting by default (i.e. one cluster with constant mean and squared exponential correlation model)
* Refactor `Egor` using [`argmin 0.7.0` solver framework](http://argmin-rs.org) `EgorSolver` can be used with `argmin::Executor` and benefit from observers and checkpointing features (#67)
* `Egor` use kriging setting by default (i.e. one cluster with constant mean and squared exponential correlation model)
* Add [notebook on Manuau Loa CO2 example](https://github.com/relf/egobox/blob/master/doc/Gpx_MaunaLoaCO2.ipynb) to show `GpMix`/`Gpx` surrogate model usage (#62)
* Use xoshiro instead of isaac random generator (#63)
* Upgrade `ndarray 0.15`, `linfa 0.6.1`, `PyO3 0.17` (#57, #64)
Expand All @@ -150,7 +152,7 @@ Version 0.4.0 - 2022-07-09

* Generate Python `egobox` module for Linux (#20)
* Improve `Egor` robustness by adding LHS optimization (#21)
* Improve `moe` with automatic number of clusters determination (#22)
* Improve `moe` with automatic number of clusters determination (#22)
* Use `linfa 0.6.0` making BLAS dependency optional (#23)
* Improve `Egor` by implementing automatic reclustering every 10-points addition (#25)
* Fix `Egor` parallel infill strategy (qEI): bad objectives and constraints gp models updste (#26)
Expand All @@ -166,14 +168,14 @@ Improve mixture of experts (#15)
* Implement `ParamGuard` for `MoeParams`
* Implement `Fit` for `MoeParams`
* Rename `MoeParams` setters

Refactor `moe`/`ego` relation (#16)

* Move `MoeFit` as `SurrogateBuilder` from `moe` to `ego`
* Implement `SurrogateBuilder` for `Moe`
* Implement `SurrogateBuilder` for `Moe`
* `Moe` uses `linfa::Fit` trait
* Rename `Evaluator` as `PreProcessor`

Refactor `MixintEgor` (#17)

* Rename `PreProcessor::eval` to `run`
Expand Down Expand Up @@ -201,6 +203,8 @@ Version 0.1.0 - 2021-11-18
Initial release

* `doe`: `LHS`, `FullFactorial`, `Random sampling`
* `gp`: Gaussian Process models with 3 regression models (constant, linear quadratic) and 4 correlation models (squared exponential, absolute exponential, matern32, matern52)
* `moe`: Mixture of Experts: find the bests mix of gps given a number of clusters regarding smooth or hard recombination
* `ego`: Contains egor optimizer which is a super EGO algorithm implemented on top of the previous elements. It implements several infill strategy: EI, WB2, WB2S and use either COBYLA or SLSQP for internal optimization.
* `gp`: Gaussian Process models with 3 regression models (constant, linear quadratic) and 4 correlation models (squared exponential, absolute exponential, matern32, matern52)
* `moe`: Mixture of Experts: find the bests mix of gps given a number of clusters
regarding smooth or hard recombination
* `ego`: Contains egor optimizer which is a super EGO algorithm implemented on top of the previous elements.
It implements several infill strategy: EI, WB2, WB2S and use either COBYLA or SLSQP for internal optimization.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing

Any input, feedback, bug report or contribution is welcome.
Any input, feedback, bug report or contribution is welcome.

When contributing to this repository, please first discuss the change you wish to make via an [issue](https://github.com/relf/egobox/issues/new). Subsequent code changes, if any, happen through pull requests.

Expand All @@ -13,7 +13,7 @@ When contributing to this repository, please first discuss the change you wish t
5. Ensure the tests pass. Run `cargo test --release --all`.
6. Create the pull request.

More information in [GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
More information in [GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).

## Code of Conduct

Expand Down
5 changes: 3 additions & 2 deletions doe/src/lhs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};

/// Kinds of Latin Hypercube Design
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub enum LhsKind {
/// sample is choosen randomly within its latin hypercube intervals
Expand All @@ -28,6 +28,7 @@ pub enum LhsKind {
/// samples locations is optimized using the Enhanced Stochastic Evolutionary algorithm (ESE)
/// See Jin, R. and Chen, W. and Sudjianto, A. (2005), “An efficient algorithm for constructing
/// optimal design of computer experiments.” Journal of Statistical Planning and Inference, 134:268-287.
#[default]
Optimized,
}

Expand Down Expand Up @@ -96,7 +97,7 @@ impl<F: Float, R: Rng + Clone> Lhs<F, R> {
}
Lhs {
xlimits: xlimits.to_owned(),
kind: LhsKind::Optimized,
kind: LhsKind::default(),
rng: Arc::new(RwLock::new(rng)),
}
}
Expand Down
9 changes: 4 additions & 5 deletions ego/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Efficient global optimization
# Efficient global optimization

[![crates.io](https://img.shields.io/crates/v/egobox-ego)](https://crates.io/crates/egobox-ego)
[![docs](https://docs.rs/egobox-ego/badge.svg)](https://docs.rs/egobox-ego)
Expand All @@ -23,11 +23,10 @@ It is a Rust port of EGO of the [SMT](https://smt.readthedocs.io) Python library

There is some usage examples in the examples/ directory. To run, use:

```
$ cargo run --release --example ackley
``` bash
cargo run --release --example ackley
```

## License

Licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0

Licensed under the Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>
19 changes: 11 additions & 8 deletions ego/src/mixint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![allow(dead_code)]
use crate::errors::{EgoError, Result};
use crate::types::{SurrogateBuilder, XType};
use egobox_doe::{FullFactorial, Lhs, Random};
use egobox_doe::{FullFactorial, Lhs, LhsKind, Random};
use egobox_moe::{
Clustered, ClusteredSurrogate, Clustering, CorrelationSpec, FullGpSurrogate, GpMixParams,
GpMixture, GpSurrogate, RegressionSpec,
Expand Down Expand Up @@ -668,12 +668,15 @@ impl MixintContext {
/// Create a mixed integer LHS
pub fn create_lhs_sampling<F: Float>(
&self,
kind: LhsKind,
seed: Option<u64>,
) -> MixintSampling<F, Lhs<F, Xoshiro256Plus>> {
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)
});
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)
})
.kind(kind);
MixintSampling {
method: lhs,
xtypes: self.xtypes.clone(),
Expand Down Expand Up @@ -740,7 +743,7 @@ mod tests {
];

let mixi = MixintContext::new(&xtypes);
let mixi_lhs = mixi.create_lhs_sampling(Some(0));
let mixi_lhs = mixi.create_lhs_sampling(LhsKind::default(), Some(0));

let actual = mixi_lhs.sample(10);
let expected = array![
Expand Down Expand Up @@ -863,7 +866,7 @@ mod tests {
let xtypes = vec![XType::Int(0, 5), XType::Cont(0., 4.), XType::Enum(4)];

let mixi = MixintContext::new(&xtypes);
let mixi_lhs = mixi.create_lhs_sampling(Some(0));
let mixi_lhs = mixi.create_lhs_sampling(LhsKind::default(), Some(0));

let n = mixi.get_unfolded_dim() * 10;
let xt = mixi_lhs.sample(n);
Expand All @@ -877,7 +880,7 @@ mod tests {
.expect("Mixint surrogate creation");

let ntest = 10;
let mixi_lhs = mixi.create_lhs_sampling(Some(42));
let mixi_lhs = mixi.create_lhs_sampling(LhsKind::default(), Some(42));

let xtest = mixi_lhs.sample(ntest);
let ytest = mixi_moe
Expand Down
18 changes: 18 additions & 0 deletions python/egobox/tests/test_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ def test_lhs(self):
)
np.testing.assert_allclose(actual, expected)

def test_all_lhs(self):
xtypes = [
egx.XSpec(egx.XType.FLOAT, [-5.0, 5.0]),
egx.XSpec(egx.XType.ENUM, tags=["blue", "red", "green"]),
egx.XSpec(egx.XType.ENUM, xlimits=[2]),
egx.XSpec(egx.XType.ORD, [0, 2, 3]),
]

for kind in [
egx.Sampling.LHS_CLASSIC,
egx.Sampling.LHS_CENTERED,
egx.Sampling.LHS_MAXIMIN,
egx.Sampling.LHS_CENTERED_MAXIMIN,
egx.Sampling.LHS,
]:
lhs = egx.sampling(kind, xtypes, 10, seed=42)
print(lhs)

def test_ffact(self):
xtypes = [
egx.XSpec(egx.XType.FLOAT, [-5.0, 5.0]),
Expand Down
49 changes: 30 additions & 19 deletions src/sampling.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use crate::types::*;
use egobox_doe::SamplingMethod;
use egobox_doe::{LhsKind, SamplingMethod};
use egobox_ego::MixintContext;
use numpy::{IntoPyArray, PyArray2};
use pyo3::prelude::*;

#[pyclass]
#[pyclass(rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(Debug, Clone, Copy)]
#[allow(clippy::upper_case_acronyms)]
pub enum Sampling {
LHS = 0,
#[allow(non_camel_case_types)]
FULL_FACTORIAL = 1,
RANDOM = 2,
Lhs = 1,
FullFactorial = 2,
Random = 3,
LhsClassic = 4,
LhsCentered = 5,
LhsMaximin = 6,
LhsCenteredMaximin = 7,
}

/// Samples generation using given method
Expand Down Expand Up @@ -53,21 +55,30 @@ pub fn sampling(
})
.collect();

let mixin = MixintContext::new(&xtypes);
let doe = match method {
Sampling::LHS => MixintContext::new(&xtypes)
.create_lhs_sampling(seed)
.sample(n_samples),
Sampling::FULL_FACTORIAL => egobox_ego::MixintContext::new(&xtypes)
.create_ffact_sampling()
.sample(n_samples),
Sampling::RANDOM => egobox_ego::MixintContext::new(&xtypes)
.create_rand_sampling(seed)
.sample(n_samples),
};
Sampling::Lhs => Box::new(mixin.create_lhs_sampling(LhsKind::default(), seed))
as Box<dyn SamplingMethod<_>>,
Sampling::LhsClassic => Box::new(mixin.create_lhs_sampling(LhsKind::Classic, seed))
as Box<dyn SamplingMethod<_>>,
Sampling::LhsMaximin => Box::new(mixin.create_lhs_sampling(LhsKind::Maximin, seed))
as Box<dyn SamplingMethod<_>>,
Sampling::LhsCentered => Box::new(mixin.create_lhs_sampling(LhsKind::Centered, seed))
as Box<dyn SamplingMethod<_>>,
Sampling::LhsCenteredMaximin => {
Box::new(mixin.create_lhs_sampling(LhsKind::CenteredMaximin, seed))
as Box<dyn SamplingMethod<_>>
}
Sampling::FullFactorial => Box::new(mixin.create_ffact_sampling()),
Sampling::Random => {
Box::new(mixin.create_rand_sampling(seed)) as Box<dyn SamplingMethod<_>>
}
}
.sample(n_samples);
doe.into_pyarray(py)
}

/// Samples generation using Latin Hypercube Sampling
/// Samples generation using optimized Latin Hypercube Sampling
///
/// # Parameters
/// xspecs: list of XSpec
Expand All @@ -84,5 +95,5 @@ pub(crate) fn lhs(
n_samples: usize,
seed: Option<u64>,
) -> &PyArray2<f64> {
sampling(py, Sampling::LHS, xspecs, n_samples, seed)
sampling(py, Sampling::Lhs, xspecs, n_samples, seed)
}
Loading