Program Listing for File newton.cpp#
↰ Return to documentation for file (source/newton.cpp)
#include "pele/newton.hpp"
#include <fstream>
#include "pele/array.hpp"
#include "pele/eigen_interface.hpp"
#include "pele/optimizer.hpp"
// Lapack for cholesky
extern "C" {
}
using pele::Array;
using namespace std;
// value at which we assume the value is zero
namespace pele {
Newton::Newton(std::shared_ptr<BasePotential> potential,
const pele::Array<double> &x0, double tol, double threshold,
bool use_rattler_mask)
: GradientOptimizer(potential, x0, tol),
_hessian(x0.size(), x0.size()),
_gradient(x0.size()),
_x(x0.size()),
_x_old(x0.size()),
_gradient_old(x0.size()),
_line_search(this, 1.0),
_tolerance(tol),
_threshold(threshold),
_nhev(0),
_use_rattler_mask(use_rattler_mask),
_not_rattlers(x0.size(), true),
_rattlers_found(false) // use step size of 1.0 for newton
{
// write pele array data into the Eigen array
for (size_t i = 0; i < x0.size(); ++i) {
_x(i) = x0[i];
}
// // Find rattlers is not necessary here in the way we're using it.
// if (_use_rattler_mask) {
// potential->find_rattlers(x0, _not_rattlers, _jammed);
// }
}
void Newton::reset(pele::Array<double> x) {
if (_use_rattler_mask) {
potential_->find_rattlers(x, _not_rattlers, _jammed);
_rattlers_found = true;
}
// write pele array data into the Eigen array
for (size_t i = 0; i < x.size(); ++i) {
_x(i) = x[i];
// TODO: Hacky, find a way to remove pele arrays in the base class
x_[i] = x[i];
}
initialize_func_gradient();
}
void Newton::one_iteration() {
// copy in from pele to make sure initialization is taken care of
eig_eq_pele(_x_old, x_);
eig_eq_pele(_gradient_old, g_);
// Wrap pele array data into the Eigen array
Array<double> hessian_pele(_hessian.data(),
_hessian.rows() * _hessian.cols());
Array<double> gradient_pele(_gradient.data(), _gradient.size());
Array<double> x_pele(_x.data(), _x.size());
if (_use_rattler_mask) {
_energy = potential_->get_energy_gradient_hessian(x_pele, gradient_pele,
hessian_pele);
_energy = potential_->get_energy_gradient_hessian_rattlers(
x_pele, gradient_pele, hessian_pele, _not_rattlers);
} else {
_energy = potential_->get_energy_gradient_hessian(x_pele, gradient_pele,
hessian_pele);
}
_nhev += 1;
cout << "gradient pele" << gradient_pele << endl;
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es =
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>(_hessian);
Eigen::VectorXd eigenvalues = es.eigenvalues();
Eigen::MatrixXd eigenvectors = es.eigenvectors();
// eigenvector corresponding to smallest eigenvalue
Eigen::VectorXd smallest_eigenvector = eigenvectors.col(0);
Eigen::CompleteOrthogonalDecomposition<Eigen::MatrixXd> cod(_hessian);
// replace eigenvalues with their absolute values
for (size_t i = 0; i < eigenvalues.size(); ++i) {
eigenvalues(i) = std::abs(eigenvalues(i));
}
// cout << "eigenvalues" << eigenvalues << endl;
// postprocess inverse Eigen
Eigen::VectorXd inv_eigenvalues(eigenvalues.size());
for (size_t i = 0; i < eigenvalues.size(); ++i) {
double mod_eig = abs(eigenvalues(i));
if (mod_eig < NUMERICAL_ZERO) {
// case for translational symmetries
inv_eigenvalues(i) = 0.0;
} else if (mod_eig < _threshold) {
// case for really small eigenvalues
inv_eigenvalues(i) = 1.0 / _threshold;
} else {
// case for normal eigenvalues
inv_eigenvalues(i) = 1.0 / mod_eig;
}
}
// find the largest eigenvalue
_step = -eigenvectors *
((eigenvectors.transpose() * _gradient).cwiseProduct(inv_eigenvalues))
.matrix();
// cout << "gradient \n" << _gradient << endl;
// cout << "step size: " << _step.norm() << endl;
double starting_norm = _step.norm();
// _step = smallest_eigenvector;
// calculate the newton step
// _step = -cod.solve(_gradient);
_x = _x_old + _step;
// use line search to find the step size
Array<double> gradient_old_pele(_gradient_old.data(), _gradient_old.size());
Array<double> x_old_pele(_x_old.data(), _x_old.size());
_line_search.set_xold_gold_(x_old_pele, gradient_old_pele);
_line_search.set_g_f_ptr(gradient_pele);
Array<double> step_pele(_step.data(), _step.size());
double stepnorm = _line_search.line_search(x_pele, step_pele);
// cout << "step \n" << _step << endl;
// cout << "gradient \n" << _gradient << endl;
// cout << "hessian \n" << _hessian << endl;
// cout << "eigenvectors \n" << eigenvectors << endl;
// cout << "eigenvalues \n" << eigenvalues << endl;
// cout << "starting norm" << starting_norm << endl;
// cout << "step norm" << stepnorm << endl;
if (stepnorm / starting_norm < 1e-10) {
throw std::runtime_error(
"rescaled step decreased by too much. Newton "
"might be in the wrong direction");
}
cout << "starting step size" << starting_norm << endl;
cout << "step size rescaled" << stepnorm << endl;
// Hacky since we're not wrapping the original pele array. but we can change
// this if this if it becomes a speed constraint/causes maintainability issues
x_ = x_pele;
g_ = gradient_pele;
iter_number_ += 1;
}
void Newton::compute_func_gradient(Array<double> x, double &func,
Array<double> gradient) {
nfev_ += 1;
// pass the arrays to the potential
if (_use_rattler_mask) {
func = potential_->get_energy_gradient(x, gradient, _not_rattlers);
} else {
func = potential_->get_energy_gradient(x, gradient);
}
}
} // namespace pele