Program Listing for File cvode.cpp#
↰ Return to documentation for file (source/cvode.cpp)
#include "pele/cvode.hpp"
#include <cassert>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <memory>
#include <ostream>
#include <stdio.h>
#include <sundials/sundials_context.h>
#include <sundials/sundials_matrix.h>
#include "cvode/cvode.h"
#include "cvode/cvode_ls.h"
#include "nvector/nvector_serial.h"
#include "pele/array.hpp"
#include "pele/base_potential.hpp"
#include "pele/optimizer.hpp"
#include "pele/preprocessor_directives.hpp"
#include "sundials/sundials_linearsolver.h"
#include "sundials/sundials_nvector.h"
#include "sunmatrix/sunmatrix_dense.h"
//#include <hoomd/HOOMDMath.h>
using namespace std;
namespace pele {
CVODEBDFOptimizer::CVODEBDFOptimizer(
std::shared_ptr<pele::BasePotential> potential,
const pele::Array<double> x0, double tol, double rtol, double atol,
HessianType hessian_type, bool use_newton_stop_criterion,
bool save_trajectory, int iterations_before_save, double newton_tol,
double offset_factor)
: ODEBasedOptimizer(potential, x0, tol, save_trajectory,
iterations_before_save),
N_size(x0.size()),
tN(1.0),
ret(0),
single_step_ret_code(0),
rtol_(rtol),
atol_(atol),
hessian_type_(hessian_type),
xold(x_.size()),
use_newton_stop_criterion_(use_newton_stop_criterion),
hessian(x0.size(), x0.size()),
newton_tol_(newton_tol),
offset_factor_(offset_factor) {
setup_cvode();
};
void CVODEBDFOptimizer::setup_cvode() {
cvode_mem = NULL;
x0_N = NULL;
A = NULL;
sunctx = NULL;
LS = NULL;
#if OPTIMIZER_DEBUG_LEVEL > 0
std::cout << "CVODE constructed with parameters: " << std::endl;
std::cout << "x0: " << x_ << std::endl;
std::cout << "tol: " << tol_ << std::endl;
std::cout << "rtol: " << rtol_ << std::endl;
std::cout << "atol: " << atol_ << std::endl;
switch (hessian_type_) {
case HessianType::DENSE:
std::cout << "hessian_type: DENSE" << std::endl;
break;
case HessianType::ITERATIVE:
std::cout << "hessian_type: ITERATIVE" << std::endl;
break;
}
std::cout << "use_newton_stop_criterion: " << use_newton_stop_criterion_
<< std::endl;
#endif
sunctx = NULL;
ret = SUNContext_Create(SUN_COMM_NULL, &sunctx);
if (check_sundials_retval(&ret, "SUNContext_Create", 1)) {
throw std::runtime_error("SUNContext_Create failed");
}
cvode_mem = CVodeCreate(CV_BDF, sunctx);
if (cvode_mem == NULL) {
std::cerr << "CVodeCreate failed to create CVODE solver" << std::endl;
exit(1);
}
time_ = 0;
Array<double> x0copy = x_.copy();
x0_N = N_Vector_eq_pele(x0copy, sunctx);
// initialization of everything CVODE needs
ret = CVodeInit(cvode_mem, f, time_, x0_N);
if (check_sundials_retval(&ret, "CVodeInit", 1)) {
throw std::runtime_error("CVODE initialization failed");
}
// initialize userdata
udata.rtol = rtol_;
udata.atol = atol_;
udata.nfev = 0;
udata.nhev = 0;
udata.pot_ = potential_;
udata.stored_grad = Array<double>(x_.size(), 0);
ret = CVodeSStolerances(cvode_mem, udata.rtol, udata.atol);
if (check_sundials_retval(&ret, "CVodeSStolerances", 1)) {
throw std::runtime_error("CVODE tolerances failed");
}
ret = CVodeSetUserData(cvode_mem, &udata);
if (check_sundials_retval(&ret, "CVodeSetUserData", 1)) {
throw std::runtime_error("CVODE user data failed");
}
if (hessian_type_ == ITERATIVE) {
LS = SUNLinSol_SPGMR(x0_N, SUN_PREC_NONE, 0, sunctx);
if (check_sundials_retval((void *)LS, "SUNLinSol_SPGMR", 0)) {
throw std::runtime_error("SUNLinSol_SPGMR failed");
}
ret = CVodeSetLinearSolver(cvode_mem, LS, NULL);
if (check_sundials_retval(&ret, "CVodeSetLinearSolver", 1)) {
throw std::runtime_error("CVODE linear solver failed");
}
} else if (hessian_type_ == DENSE) {
A = SUNDenseMatrix(N_size, N_size, sunctx);
if (check_sundials_retval((void *)A, "SUNDenseMatrix", 0)) {
throw std::runtime_error("SUNDenseMatrix failed");
}
LS = SUNLinSol_Dense(x0_N, A, sunctx);
if (check_sundials_retval((void *)LS, "SUNLinSol_Dense", 0)) {
throw std::runtime_error("SUNLinSol_Dense failed");
}
ret = CVodeSetLinearSolver(cvode_mem, LS, A);
if (check_sundials_retval(&ret, "CVodeSetLinearSolver", 1)) {
throw std::runtime_error("CVODE linear solver failed");
}
ret = CVodeSetJacFn(cvode_mem, Jac);
if (check_sundials_retval(&ret, "CVodeSetJacFn", 1)) {
throw std::runtime_error("CVODE set jacobian failed");
}
} else {
throw std::runtime_error("Unknown Hessian type");
}
g_.assign(udata.stored_grad);
ret = CVodeSetMaxNumSteps(cvode_mem, 1000000);
if (check_sundials_retval(&ret, "CVodeSetMaxNumSteps", 1)) {
throw std::runtime_error("CVODE set max num steps failed");
}
ret = CVodeSetStopTime(cvode_mem, tN);
if (check_sundials_retval(&ret, "CVodeSetStopTime", 1)) {
throw std::runtime_error("CVODE set stop time failed");
}
#if PRINT_TO_FILE == 1
trajectory_file.open("trajectory_cvode.txt");
gradient_file.open("gradient_cvode.txt");
time_file.open("time_cvode.txt");
#endif
}
CVODEBDFOptimizer::~CVODEBDFOptimizer() {
// free other sundials objects in the constructor
// reused in the move/copy constructor so the function has been extracted
free_cvode_objects();
}
void CVODEBDFOptimizer::free_cvode_objects() {
N_VDestroy(x0_N);
SUNLinSolFree(LS);
SUNMatDestroy(A);
CVodeFree(&cvode_mem);
SUNContext_Free(&sunctx);
}
void CVODEBDFOptimizer::one_iteration() {
/* advance solver just one internal step */
xold.assign(x_);
single_step_ret_code = CVode(cvode_mem, tN, x0_N, &time_, CV_ONE_STEP);
assert(static_cast<size_t>(N_VGetLength_Serial(x0_N)) == x_.size());
x_.assign(pele_eq_N_Vector(x0_N));
g_.assign(udata.stored_grad);
gradient_norm_ = (norm(g_) / sqrt(x_.size()));
f_ = udata.stored_energy;
nfev_ = udata.nfev;
Array<double> step = xold - x_;
step_norm_ = norm(step);
iter_number_ += 1;
#if PRINT_TO_FILE == 1
trajectory_file << std::setprecision(17) << x_;
time_file << std::setprecision(17) << time_ << std::endl;
gradient_file << std::setprecision(17) << g_;
#endif
}
void CVODEBDFOptimizer::add_translation_offset_2d(Eigen::MatrixXd &hessian,
double offset) {
// factor so that the added translation operators are unitary
double factor = 2.0 / hessian.rows();
offset = offset * factor;
for (long i = 0; i < hessian.rows(); ++i) {
for (long j = i + 1; j < hessian.cols(); ++j) {
if ((i % 2 == 0 && j % 2 == 0) || (i % 2 == 1 && j % 2 == 1)) {
hessian(i, j) += offset;
hessian(j, i) += offset;
}
}
}
hessian.diagonal().array() += offset;
}
bool CVODEBDFOptimizer::stop_criterion_satisfied() {
if (!func_initialized_) {
// For some reason clang throws an error here, but gcc compiles properly
initialize_func_gradient();
}
if (norm(x_) > 1e10) {
// do a safe exit
// succeeded is false, so the optimizer will
// have passed that the run has failed
std::cout << "x is too large, exiting" << std::endl;
return true;
}
if (single_step_ret_code < 0) {
// CVODE failed so, no point continuing
std::cout << "CVODE failed, exiting" << std::endl;
succeeded_ = false;
return true;
}
if (gradient_norm_ < tol_) {
if (use_newton_stop_criterion_) {
if (iter_number_ % 100 == 0) {
// Check with a more stringent hessian condition
Array<double> pele_hessian =
Array<double>(hessian.data(), hessian.size());
Eigen::Map<Eigen::VectorXd, 0, Eigen::InnerStride<1>> g_eigen(
g_.data(), int(g_.size()));
potential_->get_hessian(x_, pele_hessian);
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es =
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>(hessian);
Eigen::VectorXd eigenvalues = es.eigenvalues();
Eigen::MatrixXd eigenvectors = es.eigenvectors();
double abs_min_eigval = eigenvalues.minCoeff();
if (abs_min_eigval < 0) {
abs_min_eigval = -abs_min_eigval;
} else {
abs_min_eigval = 0;
}
// replace eigenvalues with their absolute values
for (long i = 0; i < eigenvalues.size(); ++i) {
eigenvalues(i) = std::abs(eigenvalues(i));
}
// cout << "eigenvalues" << eigenvalues << endl;
// postprocess inverse Eigenvalues
double average_eigenvalue = eigenvalues.mean();
double offset = std::max(offset_factor_ * std::abs(average_eigenvalue),
2 * abs_min_eigval);
hessian.diagonal().array() += offset;
Eigen::VectorXd newton_step(g_.size());
newton_step.setZero();
newton_step = -hessian.ldlt().solve(g_eigen);
#if OPTIMIZER_DEBUG_LEVEL > 0
std::cout << "offset = " << offset << "\n";
std::cout << "average eigenvalue = " << average_eigenvalue << "\n";
std::cout << "abs min eigenvalue = " << abs_min_eigval << "\n";
std::cout << "checking newton stop criterion\n";
std::cout << "iter number = " << iter_number_ << "\n";
std::cout << "gradient norm = " << gradient_norm_ << "\n";
std::cout << "newton step norm = " << newton_step.norm() << "\n";
#endif
if (newton_step.norm() < newton_tol_) {
#if OPTIMIZER_DEBUG_LEVEL > 0
std::cout << "converged in " << iter_number_ << " iterations\n";
std::cout << "rms = " << gradient_norm_ << "\n";
std::cout << "tol = " << tol_ << "\n";
#endif
succeeded_ = true;
return true;
} else {
return false;
}
} else {
return false;
}
} else {
#if OPTIMIZER_DEBUG_LEVEL > 0
std::cout << "converged in " << iter_number_ << " iterations\n";
std::cout << "rms = " << gradient_norm_ << "\n";
std::cout << "tol = " << tol_ << "\n";
#endif
succeeded_ = true;
return true;
}
// Wrap into a matrix
} else {
return false;
}
}
int f(double, N_Vector y, N_Vector ydot, void *user_data) {
UserData udata = (UserData)user_data;
pele::Array<double> yw = pele_eq_N_Vector(y);
// double energy = udata->pot_->get_energy_gradient(yw, g);
double *fdata = NV_DATA_S(ydot);
// g = Array<double>(fdata, NV_LENGTH_S(ydot));
// calculate negative grad g
double energy = udata->pot_->get_energy_gradient(yw, udata->stored_grad);
udata->nfev += 1;
for (size_t i = 0; i < yw.size(); ++i) {
fdata[i] = -udata->stored_grad[i];
}
udata->stored_energy = energy;
return 0;
}
int Jac(sunrealtype, N_Vector y, N_Vector, SUNMatrix J, void *user_data, N_Vector,
N_Vector, N_Vector) {
UserData udata = (UserData)user_data;
pele::Array<double> yw = pele_eq_N_Vector(y);
Array<double> g = Array<double>(yw.size());
// TODO: don't keep allocating memory for every jacobian calculation
Array<double> h = Array<double>(yw.size() * yw.size());
udata->pot_->get_energy_gradient_hessian(pele_eq_N_Vector(y), g, h);
udata->nhev += 1;
double *hessdata = SUNDenseMatrix_Data(J);
for (size_t i = 0; i < yw.size(); ++i) {
for (size_t j = 0; j < yw.size(); ++j) {
hessdata[i * yw.size() + j] = -h[i * yw.size() + j];
}
}
return 0;
};
} // namespace pele