#![no_std]
#[cfg(test)]
#[macro_use]
extern crate std;
extern crate alloc;
use core::{
cmp::Ordering,
fmt::Debug,
};
use alloc::vec::Vec;
pub const DEFAULT_EXPANSION_RATIO: f64 = 1.5;
pub const DEFAULT_COMPRESSION_RATIO: f64 = 0.5;
pub const DEFAULT_INITIAL_STEP_SIZE: f64 = 1.0;
pub const DEFAULT_NUM_ITERATIONS: usize = 100;
pub struct ProximalOptimizer {
parameters: Vec<Parameters>,
iterations: usize,
maximize: bool,
}
impl ProximalOptimizer {
pub fn new(num_parameters: usize) -> ProximalOptimizer {
let mut climb_parameters = Vec::with_capacity(num_parameters);
for _ in 0..num_parameters {
climb_parameters.push(Parameters::default())
}
ProximalOptimizer { parameters: climb_parameters,
iterations: DEFAULT_NUM_ITERATIONS,
maximize: false, }
}
pub fn get_num_parameters(&self) -> usize { self.parameters.len() }
pub fn maximize(&mut self) { self.maximize = true; }
pub fn minimize(&mut self) { self.maximize = false; }
pub fn iterations(&mut self, iterations: usize) {
self.iterations = iterations;
}
pub fn get_iterations(&self) -> usize { self.iterations }
pub fn initial_step_size(&mut self, step_size: f64) {
for param in self.parameters.iter_mut() {
param.step_size = step_size;
}
}
pub fn initial_step_sizes(&mut self,
step_sizes: &[f64])
-> Result<(), ProximalOptimizerErr> {
if step_sizes.len() != self.parameters.len() {
return Err(ProximalOptimizerErr::ParameterLengthMismatch);
}
for (i, param) in self.parameters.iter_mut().enumerate() {
param.step_size = step_sizes[i];
}
Ok(())
}
pub fn step_expansion_ratio(&mut self, step_expansion_ratio: f64) {
for param in self.parameters.iter_mut() {
param.compression_ratio = step_expansion_ratio;
}
}
pub fn step_expansion_ratios(&mut self,
step_expansion_ratio: &[f64])
-> Result<(), ProximalOptimizerErr> {
if step_expansion_ratio.len() != self.parameters.len() {
return Err(ProximalOptimizerErr::ParameterLengthMismatch);
}
for (i, param) in self.parameters.iter_mut().enumerate() {
param.expansion_ratio = step_expansion_ratio[i];
}
Ok(())
}
pub fn step_compression_ratio(&mut self, step_compression_ratio: f64) {
for param in self.parameters.iter_mut() {
param.compression_ratio = step_compression_ratio;
}
}
pub fn step_decrease_ratios(&mut self,
step_compression_ratios: &[f64])
-> Result<(), ProximalOptimizerErr> {
if step_compression_ratios.len() != self.parameters.len() {
return Err(ProximalOptimizerErr::ParameterLengthMismatch);
}
for (i, param) in self.parameters.iter_mut().enumerate() {
param.compression_ratio = step_compression_ratios[i];
}
Ok(())
}
pub fn optimize<F, T>(&self,
start: &[f64],
mut func: F)
-> Result<Vec<f64>, ProximalOptimizerErr>
where F: FnMut(&[f64]) -> T,
T: PartialOrd + Debug
{
if start.len() != self.parameters.len() {
return Err(ProximalOptimizerErr::ParameterLengthMismatch);
}
let start_fit = func(start);
let mut current_fit = func(start);
let start_cmp = start_fit.partial_cmp(&start_fit);
if start_cmp.is_none() {
return Err(ProximalOptimizerErr::StartUnorderable);
}
let mut parameters = self.parameters.clone();
let mut current_pos = Vec::<f64>::with_capacity(start.len());
current_pos.extend_from_slice(start);
let mut candidate = current_pos.clone();
let x_n = start.len();
for _train_iteration in 0..self.iterations {
current_fit = func(¤t_pos);
candidate.clear();
candidate.extend_from_slice(¤t_pos[..]);
for x_i in 0..x_n {
let old_val = candidate[x_i];
candidate[x_i] = current_pos[x_i] + parameters[x_i].step_size;
let test_fit = func(&candidate);
let cmp = match test_fit.partial_cmp(¤t_fit) {
Some(ordering) => {
if !self.maximize {
Some(ordering.reverse())
} else {
Some(ordering)
}
},
None => None,
};
match cmp {
Some(Ordering::Less) | None => {
parameters[x_i].step_size = parameters[x_i].step_size
* -1.0
* parameters[x_i].compression_ratio;
},
Some(Ordering::Greater) => {
parameters[x_i].step_size =
parameters[x_i].step_size * self.parameters[x_i].expansion_ratio;
current_pos[x_i] = candidate[x_i];
},
Some(Ordering::Equal) => {
parameters[x_i].step_size =
parameters[x_i].step_size
* self.parameters[x_i].compression_ratio;
},
}
candidate[x_i] = old_val;
}
}
let final_cmp = current_fit.partial_cmp(&start_fit);
match (final_cmp, self.maximize) {
(Some(Ordering::Greater), true) | (Some(Ordering::Less), false) => {
Ok(current_pos)
},
_ => Err(ProximalOptimizerErr::SolutionNoBetter),
}
}
}
#[derive(Copy, Clone, Debug)]
struct Parameters {
expansion_ratio: f64,
compression_ratio: f64,
step_size: f64,
}
impl Default for Parameters {
fn default() -> Self {
Parameters { expansion_ratio: DEFAULT_EXPANSION_RATIO,
compression_ratio: DEFAULT_COMPRESSION_RATIO,
step_size: DEFAULT_INITIAL_STEP_SIZE, }
}
}
#[derive(Copy, Clone, Debug)]
pub enum ProximalOptimizerErr {
ParameterLengthMismatch,
StartUnorderable,
SolutionNoBetter,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_rosenbrock() {
let mut po = ProximalOptimizer::new(2);
let glob_opt = vec![1.0, 1.0];
let tolerance = vec![0.01, 0.01];
po.iterations(10000);
let mut pos = vec![-1.2, 1.0];
println!("Start Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
pos = po.optimize(&pos, rosenbrock).unwrap();
println!("End Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
confirm_dif(&glob_opt, &pos, &tolerance);
po.iterations(40000);
let mut pos = vec![2.0, 2.0];
println!("Start Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
pos = po.optimize(&pos, rosenbrock).unwrap();
println!("End Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
confirm_dif(&glob_opt, &pos, &tolerance);
po.iterations(10000);
let mut pos = vec![0.0, 0.0];
println!("Start Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
pos = po.optimize(&pos, rosenbrock).unwrap();
println!("End Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
confirm_dif(&glob_opt, &pos, &tolerance);
po.iterations(400000);
let mut pos = vec![100.0, 100.0];
println!("Start Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
pos = po.optimize(&pos, rosenbrock).unwrap();
println!("End Position: {:?}, value: {:?}", &pos, rosenbrock(&pos));
confirm_dif(&glob_opt, &pos, &tolerance);
}
#[test]
pub fn test_simple_parabola() {
let mut po = ProximalOptimizer::new(1);
po.iterations(10);
po.maximize();
let pos = po.optimize(&vec![0.0], simple_parabola).unwrap();
println!("pos {:?}", &pos);
assert_eq!(pos, vec![49.2578125])
}
fn simple_parabola(x: &[f64]) -> f64 {
-0.5 * x[0] * x[0] + 50.0 * x[0] + 12.0
}
fn rosenbrock(x: &[f64]) -> f64 {
((1.0 - x[0]) * (1.0 - x[0])
+ 100.0 * (x[1] - x[0] * x[0]) * (x[1] - x[0] * x[0]))
}
fn confirm_dif(expected: &[f64], observed: &[f64], tolerance: &[f64]) {
assert_eq!(expected.len(), observed.len());
assert_eq!(expected.len(), tolerance.len());
for i in 0..expected.len() {
let diff = (expected[i] - observed[i]).abs();
if diff > tolerance[i] {
panic!();
}
}
}
}