use log::info;
use crate::coordinates::{Vector3D, Point};
use crate::molecule::Molecule;
use crate::ff::forcefield::Forcefield;
pub struct SteepestDecentOptimiser{
alpha: f64, iteration: usize, max_num_iterations: usize, grad_rms_tolerance: f64, energy_history: Vec<f64>, init_coordinates: Vec<Point> }
impl SteepestDecentOptimiser {
pub fn default() -> Self{
SteepestDecentOptimiser{
alpha: 0.0001,
iteration: 0,
max_num_iterations: 500,
grad_rms_tolerance: 0.1,
energy_history: Default::default(),
init_coordinates: Default::default()
}
}
pub fn from_max_iterations(max_num_iterations: usize) -> Self{
let mut optimiser = SteepestDecentOptimiser::default();
optimiser.max_num_iterations = max_num_iterations;
optimiser
}
pub fn optimise(&mut self,
molecule: &mut Molecule,
forcefield: &mut dyn Forcefield){
self.cache_initial_coordinates(molecule);
while self.iteration < self.max_num_iterations{
if self.needs_an_energy_evaluation(){
self.energy_history.push(forcefield.energy(&molecule.coordinates));
}
if self.energy_is_rising(){
self.alpha /= 2.; self.energy_history.clear();
molecule.coordinates = self.init_coordinates.clone();
}
let gradient = forcefield.gradient(&molecule.coordinates);
if self.converged(gradient){
info!("Converged in {} steps", self.iteration);
break;
}
for (i, v) in gradient.iter().enumerate(){
molecule.coordinates[i].x -= self.alpha * v.x;
molecule.coordinates[i].y -= self.alpha * v.y;
molecule.coordinates[i].z -= self.alpha * v.z;
}
self.iteration += 1;
}
}
fn converged(&self, gradient: &Vec<Vector3D>) -> bool{
SteepestDecentOptimiser::grad_rms(gradient) < self.grad_rms_tolerance
}
fn grad_rms(gradient: &Vec<Vector3D>) -> f64{
let mut sum_squares = 0.;
for v in gradient.iter(){
sum_squares += v.length();
}
(sum_squares / gradient.len() as f64).sqrt()
}
fn cache_initial_coordinates(&mut self, molecule: &Molecule){
self.init_coordinates = molecule.coordinates.clone();
}
fn needs_an_energy_evaluation(&self) -> bool{
self.energy_history.len() < 5
}
fn energy_is_rising(&mut self) -> bool{
let n = self.energy_history.len();
if n < 2{
return false;
}
self.energy_history[n-1] > self.energy_history[n-2]
}
}