Program Listing for File lbfgs.cpp#
↰ Return to documentation for file (source/lbfgs.cpp)
#include "pele/lbfgs.hpp"
#include <iostream>
#include <limits>
#include <memory>
#include "pele/array.hpp"
#include "pele/optimizer.hpp"
namespace pele {
LBFGS::LBFGS(std::shared_ptr<pele::BasePotential> potential,
const pele::Array<double> x0, double tol, int M,
bool save_trajectory, int iterations_before_save,
StopCriterionType stop_criterion)
: GradientOptimizer(potential, x0, tol, save_trajectory,
iterations_before_save, stop_criterion),
M_(M),
max_f_rise_(1e-4),
use_relative_f_(false),
rho_(M_),
H0_(0.1),
k_(0),
alpha(M_),
xold(x_.size()),
gold(x_.size()),
step(x_.size()) {
#if PRINT_TO_FILE == 1
trajectory_file.open("trajectory_lbfgs.txt");
#endif
std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);
inv_sqrt_size = 1 / sqrt(x_.size());
// allocate space for s_ and y_
s_ = Array<double>(x_.size() * M_);
y_ = Array<double>(x_.size() * M_);
}
void LBFGS::one_iteration() {
if (!func_initialized_) {
initialize_func_gradient();
}
// make a copy of the position and gradient
xold.assign(x_);
gold.assign(g_);
// get the stepsize and direction from the LBFGS algorithm
compute_lbfgs_step(step);
// reduce the stepsize if necessary
double stepnorm = backtracking_linesearch(step);
// update the LBFGS memeory
update_memory(xold, gold, x_, g_);
// print some status information
if ((iprint_ > 0) && (iter_number_ % iprint_ == 0)) {
std::cout << "lbgs: " << iter_number_ << " E " << f_ << " rms "
<< gradient_norm_ << " nfev " << nfev_ << " step norm "
<< stepnorm << std::endl;
}
iter_number_ += 1;
#if PRINT_TO_FILE == 1
trajectory_file << x_;
#endif
}
void LBFGS::update_memory(Array<double> x_old, Array<double> g_old,
Array<double> x_new, Array<double> g_new) {
// update the lbfgs memory
// This updates s_, y_, rho_, and H0_, and k_
int klocal = k_ % M_;
double ys = 0;
double yy = 0;
#pragma simd reduction(+ : ys, yy)
for (size_t j2 = 0; j2 < x_.size(); ++j2) {
size_t ind_j2 = klocal * x_.size() + j2;
y_[ind_j2] = g_new[j2] - g_old[j2];
s_[ind_j2] = x_new[j2] - x_old[j2];
ys += y_[ind_j2] * s_[ind_j2];
yy += y_[ind_j2] * y_[ind_j2];
}
if (ys == 0.) {
if (verbosity_ > 0) {
std::cout << "warning: resetting YS to 1." << std::endl;
}
ys = 1.;
}
rho_[klocal] = 1. / ys;
if (yy == 0.) {
if (verbosity_ > 0) {
std::cout << "warning: resetting YY to 1." << std::endl;
}
yy = 1.;
}
H0_ = ys / yy;
// increment k
k_ += 1;
}
void LBFGS::compute_lbfgs_step(Array<double> step) {
if (k_ == 0) {
// take a conservative first step
double gnorm = norm(g_);
if (gnorm > 1.) {
gnorm = 1. / gnorm;
}
double prefactor = -gnorm * H0_;
#pragma simd
for (size_t j2 = 0; j2 < x_.size(); ++j2) {
step[j2] = prefactor * g_[j2];
}
return;
}
// copy the gradient into step
step.assign(g_);
int jmin = std::max(0, k_ - M_);
int jmax = k_;
int i;
alpha.assign(0.0);
// loop backwards through the memory
for (int j = jmax - 1; j >= jmin; --j) {
i = j % M_;
double alpha_tmp = 0;
#pragma simd reduction(+ : alpha_tmp)
for (size_t j2 = 0; j2 < step.size(); ++j2) {
alpha_tmp += rho_[i] * s_[i * step.size() + j2] * step[j2];
}
#pragma simd
for (size_t j2 = 0; j2 < step.size(); ++j2) {
step[j2] -= alpha_tmp * y_[i * step.size() + j2];
}
alpha[i] = alpha_tmp;
}
// scale the step size by H0, invert the step to point downhill
#pragma simd
for (size_t j2 = 0; j2 < step.size(); ++j2) {
step[j2] *= -H0_;
}
// loop forwards through the memory
for (int j = jmin; j < jmax; ++j) {
i = j % M_;
double beta = 0;
#pragma simd reduction(+ : beta)
for (size_t j2 = 0; j2 < step.size(); ++j2) {
beta -= rho_[i] * y_[i * step.size() + j2] *
step[j2]; // -= due to inverted step
}
double alpha_beta = alpha[i] - beta;
#pragma simd
for (size_t j2 = 0; j2 < step.size(); ++j2) {
step[j2] -=
s_[i * step.size() + j2] * alpha_beta; // -= due to inverted step
}
}
}
double LBFGS::backtracking_linesearch(Array<double> step) {
double fnew;
// if the step is pointing uphill, invert it
if (dot(step, g_) > 0.) {
if (verbosity_ > 1) {
std::cout << "warning: step direction was uphill. inverting"
<< std::endl;
}
#pragma simd
for (size_t j2 = 0; j2 < step.size(); ++j2) {
step[j2] *= -1;
}
}
double factor = 1.;
step_norm_ = compute_pot_norm(step);
// make sure the step is no larger than maxstep_
if (factor * step_norm_ > maxstep_) {
factor = maxstep_ / step_norm_;
}
int nred;
int nred_max = 10;
for (nred = 0; nred < nred_max; ++nred) {
#pragma simd
for (size_t j2 = 0; j2 < x_.size(); ++j2) {
x_[j2] = xold[j2] + factor * step[j2];
}
compute_func_gradient(x_, fnew, g_);
double df = fnew - f_;
if (use_relative_f_) {
double absf = 1e-100;
if (f_ != 0) {
absf = std::abs(f_);
}
df /= absf;
}
if (df < max_f_rise_) {
break;
} else {
factor *= 0.5;
if (verbosity_ > 2) {
std::cout << "energy increased by " << df << " to " << fnew << " from "
<< f_ << " reducing step norm to " << factor * step_norm_
<< " H0 " << H0_ << std::endl;
}
}
}
if (nred >= nred_max) {
// possibly raise an error here
if (verbosity_ > 0) {
std::cout << "warning: the line search backtracked too many times"
<< std::endl;
}
}
f_ = fnew;
gradient_norm_ = norm(g_) * inv_sqrt_size;
return step_norm_ * factor;
}
void LBFGS::reset(pele::Array<double> &x0) {
if (x0.size() != x_.size()) {
throw std::invalid_argument(
"The number of degrees of freedom (x0.size()) "
"cannot change when calling reset()");
}
k_ = 0;
iter_number_ = 0;
nfev_ = 0;
H0_ = 0.1;
x_.assign(x0);
H0_ = 0.1;
func_initialized_ = false;
}
} // namespace pele