diff --git a/Cargo.lock b/Cargo.lock index de521f9..f8f0f43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,60 +180,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" -[[package]] -name = "matrixmultiply" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" -dependencies = [ - "autocfg", - "rawpointer", -] - -[[package]] -name = "ndarray" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "rawpointer", - "serde", -] - -[[package]] -name = "ndarray-rand" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65608f937acc725f5b164dcf40f4f0bc5d67dc268ab8a649d3002606718c4588" -dependencies = [ - "ndarray", - "rand", - "rand_distr", -] - -[[package]] -name = "num-complex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.16" @@ -254,7 +200,6 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" name = "optimal" version = "0.1.0" dependencies = [ - "ndarray", "optimal-core", "optimal-pbil", "optimal-steepest", @@ -273,7 +218,6 @@ name = "optimal-core" version = "0.1.0" dependencies = [ "blanket", - "ndarray", "paste", "replace_with", "serde", @@ -290,8 +234,6 @@ dependencies = [ "derive-bounded", "derive-getters", "derive_more", - "ndarray", - "ndarray-rand", "num-traits", "once_cell", "optimal-core", @@ -313,8 +255,6 @@ dependencies = [ "derive-bounded", "derive-getters", "derive_more", - "ndarray", - "ndarray-rand", "num-traits", "once_cell", "optimal-core", @@ -415,16 +355,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand", -] - [[package]] name = "rand_xorshift" version = "0.3.0" @@ -444,12 +374,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "redox_syscall" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index 239dd2b..814ebc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ license = "MIT" github = { repository = "justinlovinger/optimal-rs", workflow = "build" } [dependencies] -ndarray = "0.15.6" optimal-core = { path = "optimal-core" } optimal-pbil = { path = "optimal-pbil" } optimal-steepest = { path = "optimal-steepest" } diff --git a/README.md b/README.md index f3a9fa1..a615d2e 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ println!( RealDerivativeConfig::start_default_for( 2, std::iter::repeat(-10.0..=10.0).take(2), - |point| { point.map(|x| x.powi(2)).sum() }, - |point| { point.map(|x| 2.0 * x) } + |point| point.iter().map(|x| x.powi(2)).sum(), + |point| point.iter().map(|x| 2.0 * x).collect(), ) .nth(100) .unwrap() diff --git a/optimal-core/Cargo.toml b/optimal-core/Cargo.toml index 0d045be..1c65009 100644 --- a/optimal-core/Cargo.toml +++ b/optimal-core/Cargo.toml @@ -15,7 +15,6 @@ blanket = "0.3.0" streaming-iterator = "0.1.9" [dev-dependencies] -ndarray = "0.15.6" paste = "1.0.14" replace_with = "0.1.7" serde = { version = "1.0.185", features = ["derive"] } diff --git a/optimal-pbil/Cargo.toml b/optimal-pbil/Cargo.toml index 5693ec8..f93daff 100644 --- a/optimal-pbil/Cargo.toml +++ b/optimal-pbil/Cargo.toml @@ -11,15 +11,13 @@ categories = ["science", "mathematics"] license = "MIT" [features] -serde = ["dep:serde", "ndarray/serde", "rand/serde1", "rand_xoshiro/serde1"] +serde = ["dep:serde", "rand/serde1", "rand_xoshiro/serde1"] [dependencies] default-for = { path = "../default-for" } derive-bounded = { path = "../derive-bounded" } derive-getters = "0.3.0" derive_more = "0.99.17" -ndarray = "0.15.6" -ndarray-rand = "0.14.0" num-traits = "0.2.16" once_cell = "1.18.0" optimal-core = { path = "../optimal-core" } diff --git a/optimal-pbil/README.md b/optimal-pbil/README.md index 84f7035..3c20674 100644 --- a/optimal-pbil/README.md +++ b/optimal-pbil/README.md @@ -5,7 +5,6 @@ Population-based incremental learning (PBIL). ## Examples ```rust -use ndarray::prelude::*; use optimal_pbil::*; println!( diff --git a/optimal-pbil/src/lib.rs b/optimal-pbil/src/lib.rs index 72c6d45..d0551b9 100644 --- a/optimal-pbil/src/lib.rs +++ b/optimal-pbil/src/lib.rs @@ -9,7 +9,6 @@ //! # Examples //! //! ``` -//! use ndarray::prelude::*; //! use optimal_pbil::*; //! //! println!( @@ -28,7 +27,6 @@ use std::fmt::Debug; use default_for::DefaultFor; use derive_getters::Getters; use derive_more::IsVariant; -use ndarray::prelude::*; use once_cell::sync::OnceCell; pub use optimal_core::prelude::*; use rand_xoshiro::{SplitMix64, Xoshiro256PlusPlus}; @@ -47,11 +45,11 @@ pub struct MismatchedLengthError; /// A type containing an array of probabilities. pub trait Probabilities { /// Return probabilities. - fn probabilities(&self) -> &Array1; + fn probabilities(&self) -> &[Probability]; } impl Probabilities for Pbil { - fn probabilities(&self) -> &Array1 { + fn probabilities(&self) -> &[Probability] { self.state().probabilities() } } @@ -147,11 +145,11 @@ impl Pbil { impl Pbil where - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { /// Return value of the best point discovered. pub fn best_point_value(&self) -> B { - (self.obj_func)(self.best_point().view()) + (self.obj_func)(&self.best_point()) } /// Return evaluation of current state, @@ -168,7 +166,7 @@ where impl StreamingIterator for Pbil where B: PartialOrd, - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { type Item = Self; @@ -207,7 +205,7 @@ where } impl Optimizer for Pbil { - type Point = Array1; + type Point = Vec; fn best_point(&self) -> Self::Point { self.state.best_point() @@ -286,7 +284,7 @@ impl Config { /// - `obj_func`: objective function to minimize pub fn start_default_for(num_bits: usize, obj_func: F) -> Pbil where - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { Self::default_for(num_bits).start(num_bits, obj_func) } @@ -302,7 +300,7 @@ impl Config { /// - `obj_func`: objective function to minimize pub fn start(self, num_bits: usize, obj_func: F) -> Pbil where - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { Pbil::new(State::Ready(Ready::initial(num_bits)), self, obj_func) } @@ -318,7 +316,7 @@ impl Config { /// - `rng`: source of randomness pub fn start_using(self, num_bits: usize, obj_func: F, rng: &mut SplitMix64) -> Pbil where - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { Pbil::new( State::Ready(Ready::initial_using(num_bits, rng)), @@ -337,7 +335,7 @@ impl Config { /// - `state`: PBIL state to start from pub fn start_from(self, obj_func: F, state: State) -> Pbil where - F: Fn(ArrayView1) -> B, + F: Fn(&[bool]) -> B, { Pbil::new(state, self, obj_func) } @@ -345,7 +343,7 @@ impl Config { impl State { /// Return custom initial state. - pub fn new(probabilities: Array1, rng: Xoshiro256PlusPlus) -> Self { + pub fn new(probabilities: Vec, rng: Xoshiro256PlusPlus) -> Self { Self::Ready(Ready::new(probabilities, rng)) } @@ -355,22 +353,25 @@ impl State { } /// Return data to be evaluated. - pub fn evaluatee(&self) -> Option> { + pub fn evaluatee(&self) -> Option<&[bool]> { match self { - State::Ready(s) => Some(s.sample().into()), - State::Sampling(s) => Some(s.sample().into()), + State::Ready(s) => Some(s.sample()), + State::Sampling(s) => Some(s.sample()), State::Mutating(_) => None, } } /// Return the best point discovered. - pub fn best_point(&self) -> Array1 { - self.probabilities().map(|p| f64::from(*p) >= 0.5) + pub fn best_point(&self) -> Vec { + self.probabilities() + .iter() + .map(|p| f64::from(*p) >= 0.5) + .collect() } } impl Probabilities for State { - fn probabilities(&self) -> &Array1 { + fn probabilities(&self) -> &[Probability] { match &self { State::Ready(s) => s.probabilities(), State::Sampling(s) => s.probabilities(), diff --git a/optimal-pbil/src/states.rs b/optimal-pbil/src/states.rs index 77c4997..607570b 100644 --- a/optimal-pbil/src/states.rs +++ b/optimal-pbil/src/states.rs @@ -1,6 +1,4 @@ use super::types::*; -use ndarray::{prelude::*, Data}; -use ndarray_rand::RandomExt; use rand::{ distributions::{Bernoulli, Standard}, prelude::SeedableRng, @@ -19,10 +17,10 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Ready { - probabilities: Array1, + probabilities: Vec, rng: Xoshiro256PlusPlus, - distrs: Array1, - sample: Array1, + distrs: Vec, + sample: Vec, } /// PBIL state for sampling @@ -31,12 +29,12 @@ pub struct Ready { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Sampling { - probabilities: Array1, + probabilities: Vec, rng: Xoshiro256PlusPlus, - distrs: Array1, - best_sample: Array1, + distrs: Vec, + best_sample: Vec, best_value: B, - sample: Array1, + sample: Vec, samples_generated: usize, } @@ -44,7 +42,7 @@ pub struct Sampling { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Mutating { - probabilities: Array1, + probabilities: Vec, rng: Xoshiro256PlusPlus, } @@ -56,7 +54,7 @@ impl Ready { /// - `num_bits`: number of bits in each sample pub(super) fn initial(num_bits: usize) -> Self { Self::new( - Array::from_elem(num_bits, Probability::default()), + [Probability::default()].repeat(num_bits), Xoshiro256PlusPlus::from_entropy(), ) } @@ -72,17 +70,19 @@ impl Ready { R: Rng, { Self::new( - Array::from_elem(num_bits, Probability::default()), + [Probability::default()].repeat(num_bits), Xoshiro256PlusPlus::from_rng(rng).expect("RNG should initialize"), ) } /// Return custom initial state. - pub(super) fn new(probabilities: Array1, mut rng: Xoshiro256PlusPlus) -> Self { - let distrs = - probabilities.map(|p| Bernoulli::new(f64::from(*p)).expect("Invalid probability")); + pub(super) fn new(probabilities: Vec, mut rng: Xoshiro256PlusPlus) -> Self { + let distrs = probabilities + .iter() + .map(|p| Bernoulli::new(f64::from(*p)).expect("Invalid probability")) + .collect::>(); Self { - sample: distrs.map(|d| rng.sample(d)), + sample: distrs.iter().map(|d| rng.sample(d)).collect(), probabilities, rng, distrs, @@ -95,9 +95,11 @@ impl Ready { pub(super) fn to_sampling(mut self, value: B) -> Sampling { let distrs = self .probabilities - .map(|p| Bernoulli::new(f64::from(*p)).expect("Invalid probability")); + .iter() + .map(|p| Bernoulli::new(f64::from(*p)).expect("Invalid probability")) + .collect::>(); Sampling { - sample: distrs.map(|d| self.rng.sample(d)), + sample: distrs.iter().map(|d| self.rng.sample(d)).collect(), probabilities: self.probabilities, rng: self.rng, distrs, @@ -108,12 +110,12 @@ impl Ready { } /// Return probabilities. - pub fn probabilities(&self) -> &Array1 { + pub fn probabilities(&self) -> &[Probability] { &self.probabilities } /// Return sample to evaluate. - pub fn sample(&self) -> &Array1 { + pub fn sample(&self) -> &[bool] { &self.sample } } @@ -130,7 +132,7 @@ impl Sampling { (self.best_sample, self.best_value) }; Sampling { - sample: self.distrs.map(|d| self.rng.sample(d)), + sample: self.distrs.iter().map(|d| self.rng.sample(d)).collect(), probabilities: self.probabilities, rng: self.rng, distrs: self.distrs, @@ -186,12 +188,12 @@ impl Sampling { } /// Return probabilities. - pub fn probabilities(&self) -> &Array1 { + pub fn probabilities(&self) -> &[Probability] { &self.probabilities } /// Return sample to evaluate. - pub fn sample(&self) -> &Array1 { + pub fn sample(&self) -> &[bool] { &self.sample } } @@ -210,43 +212,34 @@ impl Mutating { adjust_rate: MutationAdjustRate, ) -> Ready { let distr: Bernoulli = chance.into(); - self.probabilities.zip_mut_with( - &Array::random_using(self.probabilities.len(), distr, &mut self.rng), - |p, mutate| { - if *mutate { - // `Standard` distribution excludes `1`, - // but it more efficient - // than `Uniform::new_inclusive(0., 1.)`. - // This operation is safe - // because Probability is closed under `adjust` - // with rate in [0,1]. - *p = unsafe { - Probability::new_unchecked(adjust( - adjust_rate.into(), - f64::from(*p), - self.rng.sample(Standard), - )) - } + self.probabilities.iter_mut().for_each(|p| { + if self.rng.sample(distr) { + // `Standard` distribution excludes `1`, + // but it more efficient + // than `Uniform::new_inclusive(0., 1.)`. + // This operation is safe + // because Probability is closed under `adjust` + // with rate in [0,1]. + *p = unsafe { + Probability::new_unchecked(adjust( + adjust_rate.into(), + f64::from(*p), + self.rng.sample(Standard), + )) } - }, - ); + } + }); Ready::new(self.probabilities, self.rng) } /// Return probabilities. - pub fn probabilities(&self) -> &Array1 { + pub fn probabilities(&self) -> &[Probability] { &self.probabilities } } -fn adjust_probabilities( - probabilities: &mut Array1, - adjust_rate: f64, - sample: ArrayBase, -) where - S: Data, -{ - probabilities.zip_mut_with(&sample, |p, b| { +fn adjust_probabilities(probabilities: &mut [Probability], adjust_rate: f64, sample: Vec) { + probabilities.iter_mut().zip(&sample).for_each(|(p, b)| { // This operation is safe // because Probability is closed under `adjust` // with rate in [0,1]. @@ -279,12 +272,12 @@ mod tests { adjust_rate: AdjustRate, ) { let state = Ready::new( - initial_probabilities.into(), + initial_probabilities, Xoshiro256PlusPlus::seed_from_u64(seed), ); - let value = f(state.sample().view()); + let value = f(state.sample()); let state = state.to_sampling(value); - let value = f(state.sample().view()); + let value = f(state.sample()); prop_assert!(are_valid( state.to_ready(adjust_rate, value).probabilities() )); @@ -298,7 +291,7 @@ mod tests { mutation_adjust_rate: MutationAdjustRate, ) { let state = (Mutating { - probabilities: initial_probabilities.into(), + probabilities: initial_probabilities, rng: Xoshiro256PlusPlus::seed_from_u64(seed), }) .to_ready(mutation_chance, mutation_adjust_rate); @@ -324,11 +317,11 @@ mod tests { arbitrary_from_bounded!(f64, MutationChance); arbitrary_from_bounded!(f64, MutationAdjustRate); - fn f(point: ArrayView1) -> usize { + fn f(point: &[bool]) -> usize { point.iter().filter(|x| **x).count() } - fn are_valid(probabilities: &Array1) -> bool { + fn are_valid(probabilities: &[Probability]) -> bool { probabilities .iter() .all(|p| Probability::try_from(f64::from(*p)).is_ok()) diff --git a/optimal-steepest/Cargo.toml b/optimal-steepest/Cargo.toml index 57c3ef6..2d6bc87 100644 --- a/optimal-steepest/Cargo.toml +++ b/optimal-steepest/Cargo.toml @@ -11,15 +11,13 @@ categories = ["science", "mathematics"] license = "MIT" [features] -serde = ["dep:serde", "ndarray/serde"] +serde = ["dep:serde"] [dependencies] default-for = { path = "../default-for" } derive-bounded = { path = "../derive-bounded" } derive-getters = "0.3.0" derive_more = "0.99.17" -ndarray = "0.15.6" -ndarray-rand = "0.14.0" num-traits = "0.2.16" once_cell = "1.18.0" optimal-core = { path = "../optimal-core" } diff --git a/optimal-steepest/src/backtracking_steepest.rs b/optimal-steepest/src/backtracking_steepest.rs index 2bc391b..0b1ab0e 100644 --- a/optimal-steepest/src/backtracking_steepest.rs +++ b/optimal-steepest/src/backtracking_steepest.rs @@ -7,12 +7,11 @@ //! # Examples //! //! ``` -//! use ndarray::prelude::*; //! use optimal_steepest::backtracking_steepest::*; //! //! fn main() { //! println!( -//! "{}", +//! "{:?}", //! Config::default() //! .start(std::iter::repeat(-10.0..=10.0).take(2), obj_func, |x| ( //! obj_func(x), @@ -24,18 +23,19 @@ //! ); //! } //! -//! fn obj_func(point: ArrayView1) -> f64 { -//! point.map(|x| x.powi(2)).sum() +//! fn obj_func(point: &[f64]) -> f64 { +//! point.iter().map(|x| x.powi(2)).sum() //! } //! -//! fn obj_func_d(point: ArrayView1) -> Array1 { -//! point.map(|x| 2.0 * x) +//! fn obj_func_d(point: &[f64]) -> Vec { +//! point.iter().map(|x| 2.0 * x).collect() //! } //! ``` use std::{ fmt::Debug, hint::unreachable_unchecked, + iter::Sum, ops::{Add, Div, Mul, Neg, RangeInclusive, Sub}, }; @@ -45,7 +45,6 @@ use derive_bounded::{ }; use derive_getters::Getters; use derive_more::Display; -use ndarray::{linalg::Dot, prelude::*, Data, Zip}; use num_traits::{ bounds::{LowerBounded, UpperBounded}, real::Real, @@ -104,8 +103,8 @@ impl BacktrackingSteepest { impl BacktrackingSteepest where - F: Fn(ArrayView1) -> A, - FD: Fn(ArrayView1) -> (A, Array1), + F: Fn(&[A]) -> A, + FD: Fn(&[A]) -> (A, Vec), { /// Return value of the best point discovered, /// evaluating the best point if necessary. @@ -127,18 +126,18 @@ where fn evaluate(&self) -> Evaluation { match &self.state { - State::Ready(x) => Evaluation::ValueAndDerivatives((self.obj_func_d)(x.point().into())), - State::Searching(x) => Evaluation::Value((self.obj_func)(x.point().into())), + State::Ready(x) => Evaluation::ValueAndDerivatives((self.obj_func_d)(x.point())), + State::Searching(x) => Evaluation::Value((self.obj_func)(x.point())), } } } impl StreamingIterator for BacktrackingSteepest where - A: Real + 'static, + A: Real + Sum + 'static, f64: AsPrimitive, - F: Fn(ArrayView1) -> A, - FD: Fn(ArrayView1) -> (A, Array1), + F: Fn(&[A]) -> A, + FD: Fn(&[A]) -> (A, Vec), { type Item = Self; @@ -182,10 +181,10 @@ impl Optimizer for BacktrackingSteepest where A: Clone, { - type Point = Array1; + type Point = Vec; fn best_point(&self) -> Self::Point { - self.state.best_point().into_owned() + self.state.best_point().into() } } @@ -226,7 +225,7 @@ pub struct Ready { pub struct Searching { point: Point, point_value: A, - step_direction: Array1, + step_direction: Vec, c_1_times_point_derivatives_dot_step_direction: A, step_size: A, point_at_step: Point, @@ -239,7 +238,7 @@ pub enum Evaluation { /// An objective value. Value(A), /// An objective value and point derivatives. - ValueAndDerivatives((A, Array1)), + ValueAndDerivatives((A, Vec)), } impl Config { @@ -285,8 +284,8 @@ impl Config { ) -> BacktrackingSteepest where A: Debug + SampleUniform + Real, - F: Fn(ArrayView1) -> A, - FD: Fn(ArrayView1) -> (A, Array1), + F: Fn(&[A]) -> A, + FD: Fn(&[A]) -> (A, Vec), { BacktrackingSteepest::new( self.initial_state_using(initial_bounds, &mut thread_rng()), @@ -308,8 +307,8 @@ impl Config { ) -> BacktrackingSteepest where A: Debug + SampleUniform + Real, - F: Fn(ArrayView1) -> A, - FD: Fn(ArrayView1) -> (A, Array1), + F: Fn(&[A]) -> A, + FD: Fn(&[A]) -> (A, Vec), R: Rng, { BacktrackingSteepest::new( @@ -330,8 +329,8 @@ impl Config { state: State, ) -> BacktrackingSteepest where - F: Fn(ArrayView1) -> A, - FD: Fn(ArrayView1) -> (A, Array1), + F: Fn(&[A]) -> A, + FD: Fn(&[A]) -> (A, Vec), { // Note, // this assumes states cannot be modified @@ -369,21 +368,19 @@ impl Config { impl State { /// Return data to be evaluated. - pub fn evaluatee(&self) -> ArrayView1 { - (match self { + pub fn evaluatee(&self) -> &[A] { + match self { State::Ready(x) => x.point(), State::Searching(x) => x.point(), - }) - .into() + } } /// Return the best point discovered. - pub fn best_point(&self) -> ArrayView1 { - (match self { + pub fn best_point(&self) -> &[A] { + match self { State::Ready(x) => x.best_point(), State::Searching(x) => x.best_point(), - }) - .view() + } } /// Return the value of the best point discovered, @@ -425,11 +422,11 @@ impl Ready { &self.point } - fn step_from_evaluated( + fn step_from_evaluated( self, config: &Config, point_value: A, - point_derivatives: ArrayBase, + point_derivatives: Vec, ) -> State where A: 'static @@ -439,19 +436,22 @@ impl Ready { + Add + Sub + Div - + One, - S: Data, + + One + + Sum, f64: AsPrimitive, - ArrayBase: Dot, Output = A>, { - let step_direction = point_derivatives.mapv(|x| -x); + let step_direction = point_derivatives.iter().map(|x| -*x).collect::>(); let step_size = config.initial_step_size_incr_rate * self.last_step_size; State::Searching(Searching { point_at_step: descend(&self.point, step_size, &step_direction), point: self.point, point_value, c_1_times_point_derivatives_dot_step_direction: config.c_1.0 - * point_derivatives.dot(&step_direction), + * point_derivatives + .into_iter() + .zip(step_direction.iter().copied()) + .map(|(x, y)| x * y) + .sum(), step_direction, step_size, }) @@ -490,13 +490,15 @@ impl Searching { } } -fn descend(point: &Point, step_size: A, step_direction: &Array1) -> Point +fn descend(point: &Point, step_size: A, step_direction: &[A]) -> Point where A: Clone + Add + Mul, { - Zip::from(point) - .and(step_direction) - .map_collect(|x, d| x.clone() + step_size.clone() * d.clone()) + point + .iter() + .zip(step_direction) + .map(|(x, d)| x.clone() + step_size.clone() * d.clone()) + .collect() } /// The sufficient decrease condition, @@ -627,4 +629,4 @@ where } } -type Point = Array1; +type Point = Vec; diff --git a/optimal-steepest/src/fixed_step_steepest.rs b/optimal-steepest/src/fixed_step_steepest.rs index 16aa44d..613f855 100644 --- a/optimal-steepest/src/fixed_step_steepest.rs +++ b/optimal-steepest/src/fixed_step_steepest.rs @@ -6,14 +6,15 @@ //! # Examples //! //! ``` -//! use ndarray::prelude::*; //! use optimal_steepest::fixed_step_steepest::*; //! //! println!( -//! "{}", +//! "{:?}", //! Config::new(StepSize::new(0.5).unwrap()) //! .start(std::iter::repeat(-10.0..=10.0).take(2), |point| point -//! .map(|x| 2.0 * x)) +//! .iter() +//! .map(|x| 2.0 * x) +//! .collect()) //! .nth(100) //! .unwrap() //! .best_point() @@ -23,7 +24,6 @@ use std::ops::{Mul, RangeInclusive, SubAssign}; use derive_getters::Getters; -use ndarray::prelude::*; use once_cell::sync::OnceCell; pub use optimal_core::prelude::*; use rand::{ @@ -72,23 +72,23 @@ impl FixedStepSteepest { impl FixedStepSteepest where - FD: Fn(ArrayView1) -> Array1, + FD: Fn(&[A]) -> Vec, { /// Return evaluation of current state, /// evaluating and caching if necessary. - pub fn evaluation(&self) -> &Array1 { + pub fn evaluation(&self) -> &[A] { self.evaluation_cache.get_or_init(|| self.evaluate()) } - fn evaluate(&self) -> Array1 { - (self.obj_func_d)(self.state.view()) + fn evaluate(&self) -> Vec { + (self.obj_func_d)(&self.state) } } impl StreamingIterator for FixedStepSteepest where A: Clone + SubAssign + Mul, - FD: Fn(ArrayView1) -> Array1, + FD: Fn(&[A]) -> Vec, { type Item = Self; @@ -97,9 +97,10 @@ where .evaluation_cache .take() .unwrap_or_else(|| self.evaluate()); - self.state.zip_mut_with(&evaluation, |x, d| { - *x -= self.config.step_size.clone() * d.clone() - }); + self.state + .iter_mut() + .zip(&evaluation) + .for_each(|(x, d)| *x -= self.config.step_size.clone() * d.clone()); } fn get(&self) -> Option<&Self::Item> { @@ -111,7 +112,7 @@ impl Optimizer for FixedStepSteepest where A: Clone, { - type Point = Array1; + type Point = Vec; fn best_point(&self) -> Self::Point { self.state.clone() @@ -126,7 +127,7 @@ pub struct Config { pub step_size: StepSize, } -type Point = Array1; +type Point = Vec; impl Config { /// Return a new 'Config'. @@ -150,7 +151,7 @@ impl Config { ) -> FixedStepSteepest where A: SampleUniform, - FD: Fn(ArrayView1) -> Array1, + FD: Fn(&[A]) -> Vec, { FixedStepSteepest::new( self.initial_state_using(initial_bounds, &mut thread_rng()), @@ -174,7 +175,7 @@ impl Config { ) -> FixedStepSteepest where A: SampleUniform, - FD: Fn(ArrayView1) -> Array1, + FD: Fn(&[A]) -> Vec, R: Rng, { FixedStepSteepest::new( @@ -192,7 +193,7 @@ impl Config { /// - `state`: initial point to start from pub fn start_from(self, obj_func_d: FD, state: Point) -> FixedStepSteepest where - FD: Fn(ArrayView1) -> Array1, + FD: Fn(&[A]) -> Vec, { FixedStepSteepest::new(state, self, obj_func_d) } diff --git a/src/lib.rs b/src/lib.rs index d6d8981..8db45d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,8 +55,8 @@ //! RealDerivativeConfig::start_default_for( //! 2, //! std::iter::repeat(-10.0..=10.0).take(2), -//! |point| { point.map(|x| x.powi(2)).sum() }, -//! |point| { point.map(|x| 2.0 * x) } +//! |point| point.iter().map(|x| x.powi(2)).sum(), +//! |point| point.iter().map(|x| 2.0 * x).collect(), //! ) //! .nth(100) //! .unwrap() @@ -75,7 +75,6 @@ use std::{ ops::{Add, Mul, RangeInclusive, Sub}, }; -use ndarray::prelude::*; pub use optimal_core::prelude; pub use optimal_core::prelude::*; use optimal_pbil::{ @@ -96,8 +95,8 @@ pub struct RealDerivative { #[allow(clippy::type_complexity)] inner: BacktrackingSteepest< f64, - Box) -> f64>, - Box) -> (f64, Array1)>, + Box f64>, + Box (f64, Vec)>, >, } @@ -129,7 +128,7 @@ impl StreamingIterator for RealDerivative { } impl Optimizer for RealDerivative { - type Point = Array1; + type Point = Vec; fn best_point(&self) -> Self::Point { self.inner.best_point() @@ -153,8 +152,8 @@ impl RealDerivativeConfig { obj_func_d: FD, ) -> RealDerivative where - F: Fn(ArrayView1) -> f64 + Clone + 'static, - FD: Fn(ArrayView1) -> Array1 + 'static, + F: Fn(&[f64]) -> f64 + Clone + 'static, + FD: Fn(&[f64]) -> Vec + 'static, { Self::default().start(len, initial_bounds, obj_func, obj_func_d) } @@ -178,8 +177,8 @@ impl RealDerivativeConfig { obj_func_d: FD, ) -> RealDerivative where - F: Fn(ArrayView1) -> f64 + Clone + 'static, - FD: Fn(ArrayView1) -> Array1 + 'static, + F: Fn(&[f64]) -> f64 + Clone + 'static, + FD: Fn(&[f64]) -> Vec + 'static, { RealDerivative { inner: self.inner_config(len).start( @@ -210,8 +209,8 @@ impl RealDerivativeConfig { rng: &mut SplitMix64, ) -> RealDerivative where - F: Fn(ArrayView1) -> f64 + Clone + 'static, - FD: Fn(ArrayView1) -> Array1 + 'static, + F: Fn(&[f64]) -> f64 + Clone + 'static, + FD: Fn(&[f64]) -> Vec + 'static, { RealDerivative { inner: self.inner_config(len).start_using( @@ -235,7 +234,7 @@ impl RealDerivativeConfig { #[allow(missing_debug_implementations)] pub struct BinaryDerivativeFree { #[allow(clippy::type_complexity)] - inner: UntilProbabilitiesConverged) -> f64>>>, + inner: UntilProbabilitiesConverged f64>>>, } /// Binary derivative-free optimizer configuration parameters. @@ -266,7 +265,7 @@ impl StreamingIterator for BinaryDerivativeFree { } impl Optimizer for BinaryDerivativeFree { - type Point = Array1; + type Point = Vec; fn best_point(&self) -> Self::Point { self.inner.it().best_point() @@ -283,7 +282,7 @@ impl BinaryDerivativeFreeConfig { /// - `obj_func`: objective function to minimize pub fn start_default_for(len: usize, obj_func: F) -> BinaryDerivativeFree where - F: Fn(ArrayView1) -> f64 + 'static, + F: Fn(&[bool]) -> f64 + 'static, { Self::default().start(len, obj_func) } @@ -299,7 +298,7 @@ impl BinaryDerivativeFreeConfig { /// - `obj_func`: objective function to minimize pub fn start(self, len: usize, obj_func: F) -> BinaryDerivativeFree where - F: Fn(ArrayView1) -> f64 + 'static, + F: Fn(&[bool]) -> f64 + 'static, { let (runner_config, config) = self.inner_config(len); BinaryDerivativeFree { @@ -323,7 +322,7 @@ impl BinaryDerivativeFreeConfig { rng: &mut SplitMix64, ) -> BinaryDerivativeFree where - F: Fn(ArrayView1) -> f64 + 'static, + F: Fn(&[bool]) -> f64 + 'static, { let (runner_config, config) = self.inner_config(len); BinaryDerivativeFree {