use vecfx::*;
use crate::cache::CachedProblem;
use crate::common::*;
use crate::GradientBasedMinimizer;
use crate::Progress;
use crate::Termination;
use crate::TerminationCriteria;
#[derive(Debug, Clone)]
pub struct FIRE {
pub max_step: f64,
pub f_alpha: f64,
pub alpha_start: f64,
pub f_inc: f64,
pub f_dec: f64,
pub n_min: usize,
dt: f64,
dt_max: f64,
dt_min: f64,
alpha: f64,
velocity: Option<Velocity>,
displacement: Option<Displacement>,
nsteps: usize,
termination: Termination,
scheme: MdScheme,
use_line_search: bool,
}
impl Default for FIRE {
fn default() -> Self {
FIRE {
dt_max: 1.00,
alpha_start: 0.10,
f_alpha: 0.99,
f_dec: 0.50,
f_inc: 1.10,
max_step: 0.10,
n_min: 5,
dt: 0.10,
dt_min: 0.02,
alpha: 0.10,
nsteps: 0,
velocity: None,
displacement: None,
termination: Termination::default(),
scheme: MdScheme::default(),
use_line_search: false,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum MdScheme {
VelocityVerlet,
SemiImplicitEuler,
}
impl Default for MdScheme {
fn default() -> Self {
MdScheme::VelocityVerlet
}
}
impl FIRE {
pub fn with_max_step(mut self, maxstep: f64) -> Self {
assert!(maxstep.is_sign_positive(), "step size should be a positive number!");
self.max_step = maxstep;
self
}
pub fn with_dt_min(mut self, dt_min: f64) -> Self {
self.dt_min = dt_min;
self
}
pub fn with_md_scheme(mut self, scheme: &str) -> Self {
match scheme {
"SE" => self.scheme = MdScheme::SemiImplicitEuler,
"VV" => self.scheme = MdScheme::VelocityVerlet,
"ASE" => self.scheme = MdScheme::SemiImplicitEuler,
_ => unimplemented!(),
}
self
}
pub fn with_max_cycles(mut self, n: usize) -> Self {
self.termination.max_cycles = n;
self
}
pub fn with_max_gradient_norm(mut self, gmax: f64) -> Self {
assert!(gmax.is_sign_positive(), "gmax: bad parameter!");
self.termination.max_gradient_norm = gmax;
self
}
pub fn with_line_search(mut self) -> Self {
self.use_line_search = true;
self
}
}
impl FIRE {
fn propagate<E>(mut self, force_prev: &mut [f64], force: &mut [f64], problem: &mut CachedProblem<E>) -> Self
where
E: FnMut(&[f64], &mut [f64]) -> f64,
{
let n = force.len();
let mut velocity = self.velocity.unwrap_or(Velocity(vec![0.0; n]));
let mut displacement = self.displacement.unwrap_or(Displacement(vec![0.0; n]));
velocity.take_md_step(&force_prev, &force, self.dt, self.scheme);
displacement.take_md_step(&force, &velocity, self.dt, self.scheme);
displacement.rescale(self.max_step);
let nls = if self.use_line_search {
info!("line search for optimal step size using MoreThuente algorithm.");
40
} else {
1
};
let ls = linesearch::linesearch().with_algorithm("MoreThuente");
let phi = |trial_step, out: &mut linesearch::Output| {
problem.revert();
problem.take_line_step(&displacement, trial_step);
out.fx = problem.value();
out.gx = displacement.vecdot(&problem.gradient());
Ok(())
};
let _ = ls.find(nls, phi);
force.vecncpy(problem.gradient());
force_prev.vecncpy(problem.gradient_prev());
let downhill = force.vecdot(&velocity).is_sign_positive();
velocity.adjust(force, self.alpha);
if downhill {
if self.nsteps > self.n_min {
self.dt = self.dt_max.min(self.dt * self.f_inc);
self.alpha *= self.f_alpha;
}
self.nsteps += 1;
} else {
self.dt = self.dt_min.max(self.dt * self.f_dec);
self.alpha = self.alpha_start;
self.nsteps = 0;
velocity.reset();
}
self.velocity = Some(velocity);
self.displacement = Some(displacement);
self
}
}
#[derive(Debug, Clone)]
pub(crate) struct Displacement(Vec<f64>);
impl Displacement {
pub fn take_md_step(
&mut self,
force: &[f64], velocity: &[f64], dt: f64, scheme: MdScheme,
) {
debug_assert!(
velocity.len() == force.len(),
"the sizes of input vectors are different!"
);
self.0.veccpy(velocity);
match scheme {
MdScheme::VelocityVerlet => {
self.0.vecscale(dt);
self.0.vecadd(force, 0.5 * dt.powi(2));
}
MdScheme::SemiImplicitEuler => {
self.0.vecscale(dt);
}
}
}
pub fn rescale(&mut self, max_disp: f64) {
let norm = self.0.vec2norm();
if norm > max_disp {
let s = max_disp / norm;
self.0.vecscale(s);
}
}
}
use std::ops::Deref;
impl Deref for Displacement {
type Target = Vec<f64>;
fn deref(&self) -> &Vec<f64> {
&self.0
}
}
#[derive(Debug, Clone)]
pub(crate) struct Velocity(Vec<f64>);
impl Deref for Velocity {
type Target = Vec<f64>;
fn deref(&self) -> &Vec<f64> {
&self.0
}
}
impl Velocity {
pub fn reset(&mut self) {
for i in 0..self.0.len() {
self.0[i] = 0.0;
}
}
pub fn adjust(&mut self, force: &[f64], alpha: f64) {
let vnorm = self.0.vec2norm();
let fnorm = force.vec2norm();
self.0.vecscale(1.0 - alpha);
self.0.vecadd(force, alpha * vnorm / fnorm);
}
pub fn take_md_step(
&mut self,
force_this: &[f64], force_next: &[f64], dt: f64, scheme: MdScheme,
) {
match scheme {
MdScheme::VelocityVerlet => {
self.0.vecadd(force_this, 0.5 * dt);
self.0.vecadd(force_next, 0.5 * dt);
}
MdScheme::SemiImplicitEuler => {
self.0.vecadd(force_this, dt);
}
}
}
}
impl GradientBasedMinimizer for FIRE {
fn minimize_alt<E, G>(mut self, x: &mut [f64], mut f: E, mut stopping: Option<G>)
where
E: FnMut(&[f64], &mut [f64]) -> f64,
G: TerminationCriteria,
{
let mut problem = CachedProblem::new(x, f);
if problem.gradient().vec2norm() <= self.termination.max_gradient_norm {
info!("already converged.");
return;
}
let mut force = problem.gradient().to_vec();
force.vecscale(-1.0);
let mut force_prev = force.clone();
for i in 1.. {
self = self.propagate(&mut force_prev, &mut force, &mut problem);
if let Some(ref displ) = self.displacement {
let fx = problem.value();
let progress = Progress {
x,
fx,
step: 1.0,
niter: i,
gx: &force,
gnorm: force.vec2norm(),
displacement: displ,
ncall: problem.ncalls(),
};
if let Some(ref mut stopping) = stopping {
if stopping.met(&progress) {
break;
}
}
if self.termination.met(&progress) {
info!("normal termination!");
break;
}
} else {
unreachable!()
}
}
}
}
use crate::base::Problem;
impl FIRE {
fn propagate_new<U>(mut self, problem: &mut Problem<U>) -> Self {
let mut force = problem.gradient().to_vec();
force.vecscale(-1.0);
let mut force_prev = force.clone();
let n = force.len();
let mut velocity = self.velocity.unwrap_or(Velocity(vec![0.0; n]));
let mut displacement = self.displacement.unwrap_or(Displacement(vec![0.0; n]));
velocity.take_md_step(&force_prev, &force, self.dt, self.scheme);
displacement.take_md_step(&force, &velocity, self.dt, self.scheme);
displacement.rescale(self.max_step);
let nls = if self.use_line_search {
info!("line search for optimal step size using MoreThuente algorithm.");
40
} else {
1
};
let ls = linesearch::linesearch().with_algorithm("MoreThuente");
let phi = |trial_step, out: &mut linesearch::Output| {
problem.revert();
problem.take_line_step(&displacement, trial_step);
out.fx = problem.value();
out.gx = displacement.vecdot(&problem.gradient());
Ok(())
};
let _ = ls.find(nls, phi);
force.vecncpy(problem.gradient());
force_prev.vecncpy(problem.gradient_prev());
let downhill = force.vecdot(&velocity).is_sign_positive();
velocity.adjust(&force, self.alpha);
if downhill {
if self.nsteps > self.n_min {
self.dt = self.dt_max.min(self.dt * self.f_inc);
self.alpha *= self.f_alpha;
}
self.nsteps += 1;
} else {
self.dt = self.dt_min.max(self.dt * self.f_dec);
self.alpha = self.alpha_start;
self.nsteps = 0;
velocity.reset();
}
self.velocity = Some(velocity);
self.displacement = Some(displacement);
self
}
}
use crate::base::{EvaluateFunction, Input, Output};
pub struct FireIter<'a, U> {
fire: Option<FIRE>,
problem: Option<Problem<'a, U>>,
}
impl<'a, U> FireIter<'a, U> {
fn next_iter(&mut self) {
let mut problem = self.problem.take().expect("fire problem");
let mut fire = self.fire.take().expect("fire fire");
self.fire = fire.propagate_new(&mut problem).into();
self.problem = problem.into();
}
}
impl FIRE {
pub fn minimize_iter<'a, E: 'a, U>(mut self, x0: Vec<f64>, f: E) -> FireIter<'a, U>
where
E: EvaluateFunction<U>,
{
FireIter {
fire: self.into(),
problem: Problem::new(x0, f).into(),
}
}
}
impl<'a, U> Iterator for FireIter<'a, U> {
type Item = crate::base::Progress<U>;
fn next(&mut self) -> Option<Self::Item> {
let gnorm = self.problem.as_mut().unwrap().gradient().vec2norm();
if gnorm <= self.fire.as_ref().unwrap().termination.max_gradient_norm {
info!("already converged.");
return None;
}
self.next_iter();
let problem = self.problem.as_mut().unwrap();
let fx = problem.value();
let ncalls = problem.ncalls();
let data = problem.user_data.take().expect("user data");
let progress = crate::base::Progress {
fx,
gnorm,
ncalls,
extra: data,
};
Some(progress)
}
}