lammps/lib/colvars/colvarbias_abf.cpp

493 lines
15 KiB
C++

/********************************************************************************
* Implementation of the ABF and histogram biases *
********************************************************************************/
#include "colvarmodule.h"
#include "colvar.h"
#include "colvarbias_abf.h"
/// ABF bias constructor; parses the config file
colvarbias_abf::colvarbias_abf (std::string const &conf, char const *key)
: colvarbias (conf, key),
gradients (NULL),
samples (NULL)
{
if (cvm::temperature() == 0.0)
cvm::log ("WARNING: ABF should not be run without a thermostat or at 0 Kelvin!\n");
// ************* parsing general ABF options ***********************
get_keyval (conf, "applyBias", apply_bias, true);
if (!apply_bias) cvm::log ("WARNING: ABF biases will *not* be applied!\n");
get_keyval (conf, "updateBias", update_bias, true);
if (!update_bias) cvm::log ("WARNING: ABF biases will *not* be updated!\n");
get_keyval (conf, "hideJacobian", hide_Jacobian, false);
if (hide_Jacobian) {
cvm::log ("Jacobian (geometric) forces will be handled internally.\n");
} else {
cvm::log ("Jacobian (geometric) forces will be included in reported free energy gradients.\n");
}
get_keyval (conf, "fullSamples", full_samples, 200);
if ( full_samples <= 1 ) full_samples = 1;
min_samples = full_samples / 2;
// full_samples - min_samples >= 1 is guaranteed
get_keyval (conf, "inputPrefix", input_prefix, std::vector<std::string> ());
get_keyval (conf, "outputFreq", output_freq, cvm::restart_out_freq);
get_keyval (conf, "historyFreq", history_freq, 0);
b_history_files = (history_freq > 0);
// ************* checking the associated colvars *******************
if (colvars.size() == 0) {
cvm::fatal_error ("Error: no collective variables specified for the ABF bias.\n");
}
for (size_t i = 0; i < colvars.size(); i++) {
if (colvars[i]->type() != colvarvalue::type_scalar) {
cvm::fatal_error ("Error: ABF bias can only use scalar-type variables.\n");
}
colvars[i]->enable (colvar::task_gradients);
if (update_bias) {
// Request calculation of system force (which also checks for availability)
colvars[i]->enable (colvar::task_system_force);
if (!colvars[i]->tasks[colvar::task_extended_lagrangian]) {
// request computation of Jacobian force
colvars[i]->enable (colvar::task_Jacobian_force);
// request Jacobian force as part as system force
// except if the user explicitly requires the "silent" Jacobian
// correction AND the colvar has a single component
if (hide_Jacobian) {
if (colvars[i]->n_components() > 1) {
cvm::log ("WARNING: colvar \"" + colvars[i]->name
+ "\" has multiple components; reporting its Jacobian forces\n");
colvars[i]->enable (colvar::task_report_Jacobian_force);
}
} else {
colvars[i]->enable (colvar::task_report_Jacobian_force);
}
}
}
// Here we could check for orthogonality of the Cartesian coordinates
// and make it just a warning if some parameter is set?
}
bin.assign (colvars.size(), 0);
force_bin.assign (colvars.size(), 0);
force = new cvm::real [colvars.size()];
// Construct empty grids based on the colvars
samples = new colvar_grid_count (colvars);
gradients = new colvar_grid_gradient (colvars);
gradients->samples = samples;
samples->has_parent_data = true;
// If custom grids are provided, read them
if ( input_prefix.size() > 0 ) {
read_gradients_samples ();
}
cvm::log ("Finished ABF setup.\n");
}
/// Destructor
colvarbias_abf::~colvarbias_abf()
{
if (samples) {
delete samples;
samples = NULL;
}
if (gradients) {
delete gradients;
gradients = NULL;
}
delete [] force;
}
/// Update the FE gradient, compute and apply biasing force
/// also output data to disk if needed
cvm::real colvarbias_abf::update()
{
if (cvm::debug()) cvm::log ("Updating ABF bias " + this->name);
if (cvm::step_relative() == 0) {
// At first timestep, do only:
// initialization stuff (file operations relying on n_abf_biases
// compute current value of colvars
if ( cvm::n_abf_biases == 1 && cvm::n_meta_biases == 0 ) {
// This is the only ABF bias
output_prefix = cvm::output_prefix;
} else {
output_prefix = cvm::output_prefix + "." + this->name;
}
for (size_t i=0; i<colvars.size(); i++) {
bin[i] = samples->current_bin_scalar(i);
}
} else {
for (size_t i=0; i<colvars.size(); i++) {
bin[i] = samples->current_bin_scalar(i);
}
if ( update_bias && samples->index_ok (force_bin) ) {
// Only if requested and within bounds of the grid...
for (size_t i=0; i<colvars.size(); i++) { // get forces (lagging by 1 timestep) from colvars
force[i] = colvars[i]->system_force();
}
gradients->acc_force (force_bin, force);
}
}
// save bin for next timestep
force_bin = bin;
// Reset biasing forces from previous timestep
for (size_t i=0; i<colvars.size(); i++) {
colvar_forces[i].reset();
}
// Compute and apply the new bias, if applicable
if ( apply_bias && samples->index_ok (bin) ) {
size_t count = samples->value (bin);
cvm::real fact = 1.0;
// Factor that ensures smooth introduction of the force
if ( count < full_samples ) {
fact = ( count < min_samples) ? 0.0 :
(cvm::real (count - min_samples)) / (cvm::real (full_samples - min_samples));
}
const cvm::real * grad = &(gradients->value (bin));
if ( fact != 0.0 ) {
if ( (colvars.size() == 1) && colvars[0]->periodic_boundaries() ) {
// Enforce a zero-mean bias on periodic, 1D coordinates
colvar_forces[0].real_value += fact * (grad[0] / cvm::real (count) - gradients->average ());
} else {
for (size_t i=0; i<colvars.size(); i++) {
// subtracting the mean force (opposite of the FE gradient) means adding the gradient
colvar_forces[i].real_value += fact * grad[i] / cvm::real (count);
// without .real_value, the above would do (cheap) runtime type checking
}
}
}
}
if (output_freq && (cvm::step_absolute() % output_freq) == 0) {
if (cvm::debug()) cvm::log ("ABF bias trying to write gradients and samples to disk");
write_gradients_samples (output_prefix);
}
if (b_history_files && (cvm::step_absolute() % history_freq) == 0) {
// append to existing file only if cvm::step_absolute() > 0
// otherwise, backup and replace
write_gradients_samples (output_prefix + ".hist", (cvm::step_absolute() > 0));
}
return 0.0; // TODO compute bias energy whenever possible (i.e. 1D with updateBias off)
}
void colvarbias_abf::write_gradients_samples (const std::string &prefix, bool append)
{
std::string samples_out_name = prefix + ".count";
std::string gradients_out_name = prefix + ".grad";
std::ios::openmode mode = (append ? std::ios::app : std::ios::out);
std::ofstream samples_os;
std::ofstream gradients_os;
if (!append) cvm::backup_file (samples_out_name.c_str());
samples_os.open (samples_out_name.c_str(), mode);
if (!samples_os.good()) cvm::fatal_error ("Error opening ABF samples file " + samples_out_name + " for writing");
samples->write_multicol (samples_os);
samples_os.close ();
if (!append) cvm::backup_file (gradients_out_name.c_str());
gradients_os.open (gradients_out_name.c_str(), mode);
if (!gradients_os.good()) cvm::fatal_error ("Error opening ABF gradient file " + gradients_out_name + " for writing");
gradients->write_multicol (gradients_os);
gradients_os.close ();
if (colvars.size () == 1) {
std::string pmf_out_name = prefix + ".pmf";
if (!append) cvm::backup_file (pmf_out_name.c_str());
std::ofstream pmf_os;
// Do numerical integration and output a PMF
pmf_os.open (pmf_out_name.c_str(), mode);
if (!pmf_os.good()) cvm::fatal_error ("Error opening pmf file " + pmf_out_name + " for writing");
gradients->write_1D_integral (pmf_os);
pmf_os << std::endl;
pmf_os.close ();
}
return;
}
void colvarbias_abf::read_gradients_samples ()
{
std::string samples_in_name, gradients_in_name;
for ( size_t i = 0; i < input_prefix.size(); i++ ) {
samples_in_name = input_prefix[i] + ".count";
gradients_in_name = input_prefix[i] + ".grad";
// For user-provided files, the per-bias naming scheme may not apply
std::ifstream is;
cvm::log ("Reading sample count from " + samples_in_name + " and gradients from " + gradients_in_name);
is.open (samples_in_name.c_str());
if (!is.good()) cvm::fatal_error ("Error opening ABF samples file " + samples_in_name + " for reading");
samples->read_multicol (is, true);
is.close ();
is.clear();
is.open (gradients_in_name.c_str());
if (!is.good()) cvm::fatal_error ("Error opening ABF gradient file " + gradients_in_name + " for reading");
gradients->read_multicol (is, true);
is.close ();
}
return;
}
std::ostream & colvarbias_abf::write_restart (std::ostream& os)
{
std::ios::fmtflags flags (os.flags ());
os.setf(std::ios::fmtflags (0), std::ios::floatfield); // default floating-point format
os << "abf {\n"
<< " configuration {\n"
<< " name " << this->name << "\n";
os << " }\n";
os << "samples\n";
samples->write_raw (os, 8);
os << "\ngradient\n";
gradients->write_raw (os);
os << "}\n\n";
os.flags (flags);
return os;
}
std::istream & colvarbias_abf::read_restart (std::istream& is)
{
if ( input_prefix.size() > 0 ) {
cvm::fatal_error ("ERROR: cannot provide both inputPrefix and restart information (colvarsInput)");
}
size_t const start_pos = is.tellg();
cvm::log ("Restarting ABF bias \""+
this->name+"\".\n");
std::string key, brace, conf;
if ( !(is >> key) || !(key == "abf") ||
!(is >> brace) || !(brace == "{") ||
!(is >> colvarparse::read_block ("configuration", conf)) ) {
cvm::log ("Error: in reading restart configuration for ABF bias \""+
this->name+"\" at position "+
cvm::to_str (is.tellg())+" in stream.\n");
is.clear();
is.seekg (start_pos, std::ios::beg);
is.setstate (std::ios::failbit);
return is;
}
std::string name = "";
if ( (colvarparse::get_keyval (conf, "name", name, std::string (""), colvarparse::parse_silent)) &&
(name != this->name) )
cvm::fatal_error ("Error: in the restart file, the "
"\"abf\" block has wrong name (" + name + ")\n");
if ( name == "" ) {
cvm::fatal_error ("Error: \"abf\" block in the restart file has no name.\n");
}
if ( !(is >> key) || !(key == "samples")) {
cvm::log ("Error: in reading restart configuration for ABF bias \""+
this->name+"\" at position "+
cvm::to_str (is.tellg())+" in stream.\n");
is.clear();
is.seekg (start_pos, std::ios::beg);
is.setstate (std::ios::failbit);
return is;
}
if (! samples->read_raw (is)) {
samples->read_raw_error();
}
if ( !(is >> key) || !(key == "gradient")) {
cvm::log ("Error: in reading restart configuration for ABF bias \""+
this->name+"\" at position "+
cvm::to_str (is.tellg())+" in stream.\n");
is.clear();
is.seekg (start_pos, std::ios::beg);
is.setstate (std::ios::failbit);
return is;
}
if (! gradients->read_raw (is)) {
gradients->read_raw_error();
}
is >> brace;
if (brace != "}") {
cvm::fatal_error ("Error: corrupt restart information for ABF bias \""+
this->name+"\": no matching brace at position "+
cvm::to_str (is.tellg())+" in the restart file.\n");
is.setstate (std::ios::failbit);
}
return is;
}
/// Histogram "bias" constructor
colvarbias_histogram::colvarbias_histogram (std::string const &conf, char const *key)
: colvarbias (conf, key),
grid (NULL)
{
get_keyval (conf, "outputfreq", output_freq, cvm::restart_out_freq);
if ( output_freq == 0 ) {
cvm::fatal_error ("User required histogram with zero output frequency");
}
grid = new colvar_grid_count (colvars);
bin.assign (colvars.size(), 0);
out_name = cvm::output_prefix + "." + this->name + ".dat";
cvm::log ("Histogram will be written to file " + out_name);
cvm::log ("Finished histogram setup.\n");
}
/// Destructor
colvarbias_histogram::~colvarbias_histogram()
{
if (grid_os.good()) grid_os.close();
if (grid) {
delete grid;
grid = NULL;
}
}
/// Update the grid
cvm::real colvarbias_histogram::update()
{
if (cvm::debug()) cvm::log ("Updating Grid bias " + this->name);
for (size_t i=0; i<colvars.size(); i++) {
bin[i] = grid->current_bin_scalar(i);
}
if ( grid->index_ok (bin) ) { // Only within bounds of the grid...
grid->incr_count (bin);
}
if (output_freq && (cvm::step_absolute() % output_freq) == 0) {
if (cvm::debug()) cvm::log ("Histogram bias trying to write grid to disk");
grid_os.open (out_name.c_str());
if (!grid_os.good()) cvm::fatal_error ("Error opening histogram file " + out_name + " for writing");
grid->write_multicol (grid_os);
grid_os.close ();
}
return 0.0; // no bias energy for histogram
}
std::istream & colvarbias_histogram::read_restart (std::istream& is)
{
size_t const start_pos = is.tellg();
cvm::log ("Restarting collective variable histogram \""+
this->name+"\".\n");
std::string key, brace, conf;
if ( !(is >> key) || !(key == "histogram") ||
!(is >> brace) || !(brace == "{") ||
!(is >> colvarparse::read_block ("configuration", conf)) ) {
cvm::log ("Error: in reading restart configuration for histogram \""+
this->name+"\" at position "+
cvm::to_str (is.tellg())+" in stream.\n");
is.clear();
is.seekg (start_pos, std::ios::beg);
is.setstate (std::ios::failbit);
return is;
}
int id = -1;
std::string name = "";
if ( (colvarparse::get_keyval (conf, "name", name, std::string (""), colvarparse::parse_silent)) &&
(name != this->name) )
cvm::fatal_error ("Error: in the restart file, the "
"\"histogram\" block has a wrong name: different system?\n");
if ( (id == -1) && (name == "") ) {
cvm::fatal_error ("Error: \"histogram\" block in the restart file "
"has no name.\n");
}
if ( !(is >> key) || !(key == "grid")) {
cvm::log ("Error: in reading restart configuration for histogram \""+
this->name+"\" at position "+
cvm::to_str (is.tellg())+" in stream.\n");
is.clear();
is.seekg (start_pos, std::ios::beg);
is.setstate (std::ios::failbit);
return is;
}
if (! grid->read_raw (is)) {
grid->read_raw_error();
}
is >> brace;
if (brace != "}") {
cvm::fatal_error ("Error: corrupt restart information for ABF bias \""+
this->name+"\": no matching brace at position "+
cvm::to_str (is.tellg())+" in the restart file.\n");
is.setstate (std::ios::failbit);
}
return is;
}
std::ostream & colvarbias_histogram::write_restart (std::ostream& os)
{
os << "histogram {\n"
<< " configuration {\n"
<< " name " << this->name << "\n";
os << " }\n";
os << "grid\n";
grid->write_raw (os, 8);
os << "}\n\n";
return os;
}