/// -*- c++ -*- #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) { // TODO relax this in case of VMD plugin 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 ()); get_keyval(conf, "outputFreq", output_freq, cvm::restart_out_freq); get_keyval(conf, "historyFreq", history_freq, 0); b_history_files = (history_freq > 0); // shared ABF get_keyval(conf, "shared", shared_on, false); if (shared_on) { if (!cvm::replica_enabled() || cvm::replica_num() <= 1) cvm::error("Error: shared ABF requires more than one replica."); else cvm::log("shared ABF will be applied among "+ cvm::to_str(cvm::replica_num()) + " replicas.\n"); // If shared_freq is not set, we default to output_freq get_keyval(conf, "sharedFreq", shared_freq, output_freq); } // ************* checking the associated colvars ******************* if (colvars.size() == 0) { cvm::error("Error: no collective variables specified for the ABF bias.\n"); } for (size_t i = 0; i < colvars.size(); i++) { if (colvars[i]->value().type() != colvarvalue::type_scalar) { cvm::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 // ultimately, will be done regardless of extended Lagrangian // and colvar should then just report zero 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? } if (get_keyval(conf, "maxForce", max_force)) { if (max_force.size() != colvars.size()) { cvm::error("Error: Number of parameters to maxForce does not match number of colvars."); } for (size_t i=0; isamples = samples; samples->has_parent_data = true; // For shared ABF, we store a second set of grids. // This used to be only if "shared" was defined, // but now we allow calling share externally (e.g. from Tcl). last_samples = new colvar_grid_count(colvars); last_gradients = new colvar_grid_gradient(colvars); last_gradients->samples = last_samples; last_samples->has_parent_data = true; shared_last_step = -1; // 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; } // shared ABF // We used to only do this if "shared" was defined, // but now we can call shared externally if (last_samples) { delete last_samples; last_samples = NULL; } if (last_gradients) { delete last_gradients; last_gradients = NULL; } delete [] force; if (cvm::n_abf_biases > 0) cvm::n_abf_biases -= 1; } /// 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 for (size_t i=0; icurrent_bin_scalar(i); } } else { for (size_t i=0; icurrent_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; isystem_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; iindex_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 // in other words: boundary condition is that the biasing potential is periodic colvar_forces[0].real_value = fact * (grad[0] / cvm::real(count) - gradients->average()); } else { for (size_t i=0; i max_force[i] * max_force[i] ) { colvar_forces[i].real_value = (colvar_forces[i].real_value > 0 ? max_force[i] : -1.0 * max_force[i]); } } } } } // update the output prefix; TODO: move later to setup_output() function 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; } 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) { cvm::log("ABFHISTORYFILE "+cvm::to_str(cvm::step_absolute())); // file already exists iff cvm::step_relative() > 0 // otherwise, backup and replace write_gradients_samples(output_prefix + ".hist", (cvm::step_relative() > 0)); } if (shared_on && shared_last_step >= 0 && cvm::step_absolute() % shared_freq == 0) { // Share gradients and samples for shared ABF. replica_share(); } // Prepare for the first sharing. if (shared_last_step < 0) { // Copy the current gradient and count values into last. last_gradients->copy_grid(*gradients); last_samples->copy_grid(*samples); shared_last_step = cvm::step_absolute(); cvm::log("Prepared sample and gradient buffers at step "+cvm::to_str(cvm::step_absolute())+"."); } return 0.0; } int colvarbias_abf::replica_share() { int p; if ( !cvm::replica_enabled() ) { cvm::error("Error: shared ABF: No replicas.\n"); return COLVARS_ERROR; } // We must have stored the last_gradients and last_samples. if (shared_last_step < 0 ) { cvm::error("Error: shared ABF: Tried to apply shared ABF before any sampling had occurred.\n"); return COLVARS_ERROR; } // Share gradients for shared ABF. cvm::log("shared ABF: Sharing gradient and samples among replicas at step "+cvm::to_str(cvm::step_absolute()) ); // Count of data items. size_t data_n = gradients->raw_data_num(); size_t samp_start = data_n*sizeof(cvm::real); size_t msg_total = data_n*sizeof(size_t) + samp_start; char* msg_data = new char[msg_total]; if (cvm::replica_index() == 0) { // Replica 0 collects the delta gradient and count from the others. for (p = 1; p < cvm::replica_num(); p++) { // Receive the deltas. cvm::replica_comm_recv(msg_data, msg_total, p); // Map the deltas from the others into the grids. last_gradients->raw_data_in((cvm::real*)(&msg_data[0])); last_samples->raw_data_in((size_t*)(&msg_data[samp_start])); // Combine the delta gradient and count of the other replicas // with Replica 0's current state (including its delta). gradients->add_grid( *last_gradients ); samples->add_grid( *last_samples ); } // Now we must send the combined gradient to the other replicas. gradients->raw_data_out((cvm::real*)(&msg_data[0])); samples->raw_data_out((size_t*)(&msg_data[samp_start])); for (p = 1; p < cvm::replica_num(); p++) { cvm::replica_comm_send(msg_data, msg_total, p); } } else { // All other replicas send their delta gradient and count. // Calculate the delta gradient and count. last_gradients->delta_grid(*gradients); last_samples->delta_grid(*samples); // Cast the raw char data to the gradient and samples. last_gradients->raw_data_out((cvm::real*)(&msg_data[0])); last_samples->raw_data_out((size_t*)(&msg_data[samp_start])); cvm::replica_comm_send(msg_data, msg_total, 0); // We now receive the combined gradient from Replica 0. cvm::replica_comm_recv(msg_data, msg_total, 0); // We sync to the combined gradient computed by Replica 0. gradients->raw_data_in((cvm::real*)(&msg_data[0])); samples->raw_data_in((size_t*)(&msg_data[samp_start])); } // Without a barrier it's possible that one replica starts // share 2 when other replicas haven't finished share 1. cvm::replica_comm_barrier(); // Done syncing the replicas. delete[] msg_data; // Copy the current gradient and count values into last. last_gradients->copy_grid(*gradients); last_samples->copy_grid(*samples); shared_last_step = cvm::step_absolute(); return COLVARS_OK; } 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); cvm::ofstream samples_os; cvm::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.is_open()) { cvm::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.is_open()) { cvm::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()); cvm::ofstream pmf_os; // Do numerical integration and output a PMF pmf_os.open(pmf_out_name.c_str(), mode); if (!pmf_os.is_open()) cvm::error("Error opening pmf file " + pmf_out_name + " for writing"); gradients->write_1D_integral(pmf_os); pmf_os << std::endl; pmf_os.close(); } return; } // For Tcl implementation of selection rules. /// Give the total number of bins for a given bias. int colvarbias_abf::bin_num() { return samples->number_of_points(0); } /// Calculate the bin index for a given bias. int colvarbias_abf::current_bin() { return samples->current_bin_scalar(0); } /// Give the count at a given bin index. int colvarbias_abf::bin_count(int bin_index) { if (bin_index < 0 || bin_index >= bin_num()) { cvm::error("Error: Tried to get bin count from invalid bin index "+cvm::to_str(bin_index)); return -1; } std::vector ix(1,(int)bin_index); return samples->value(ix); } 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.is_open()) cvm::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.is_open()) cvm::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::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::error("Error: in the restart file, the " "\"abf\" block has wrong name(" + name + ")\n"); if ( name == "" ) { cvm::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)) { is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } 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)) { is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } is >> brace; if (brace != "}") { cvm::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; }