Update FixColvars to expand usage of fix_modify commands

See https://github.com/Colvars/colvars/pull/418

Also moving inthash code to a separate file to simplify future refactoring
This commit is contained in:
Giacomo Fiorin
2024-08-06 01:05:51 +02:00
parent 72a0992054
commit 278accd9ea
8 changed files with 779 additions and 566 deletions

View File

@ -8,151 +8,228 @@ Syntax
.. code-block:: LAMMPS
fix ID group-ID colvars configfile keyword values ...
fix ID group-ID colvars *configfile* keyword value ...
* ID, group-ID are documented in :doc:`fix <fix>` command
* colvars = style name of this fix command
* configfile = the configuration file for the colvars module
* keyword = *input* or *output* or *seed* or *unwrap* or *tstat*
* *ID*, *group-ID* are documented in :doc:`fix <fix>` command
* "colvars" = style name of this fix command
* *configfile* = configuration file for Colvars (use "*none*" to provide it inline)
* keyword = *output* or *input* or *unwrap* or *tstat* or *seed*
.. parsed-literal::
*input* arg = colvars.state file name or prefix or NULL (default: NULL)
*output* arg = output filename prefix (default: out)
*seed* arg = seed for random number generator (default: 1966)
*unwrap* arg = *yes* or *no*
use unwrapped coordinates in collective variables (default: yes)
*tstat* arg = fix id of a thermostat or NULL (default: NULL)
*output* value = state filename/prefix for Colvars (default: "out")
*input* value = input state filename/prefix for Colvars (optional, default: "NULL")
*unwrap* value = "yes" or "no" (default: "yes")
*tstat* value = fix ID of thermostat applied to relevant atoms (default: "NULL")
*seed* value = seed for random number generator (default: 1966)
Examples
""""""""
.. code-block:: LAMMPS
fix mtd all colvars peptide.colvars.inp seed 2122 input peptide.colvars.state output peptide
fix abf all colvars colvars.inp tstat 1
# Create the fix using a config file, set prefix for its output files
fix Colvars all colvars colvars.inp output ${JOB}
# Communicate the LAMMPS target temperature to the Colvars module
fix_modify Colvars tstat NPT
# Add a new restraint specific to this LAMMPS run
fix_modify Colvars config """
harmonic {
name restraint
colvars distance1 distance2
centers ${ref1} ${ref2}
forceConstant ${kappa}
}"""
Description
"""""""""""
This fix interfaces LAMMPS to the collective variables (Colvars)
library, which allows to calculate potentials of mean force (PMFs) for
any set of colvars, using sampling methods, including but not limited to
Adaptive Biasing Force (ABF), metadynamics (MtD), Steered Molecular
Dynamics (SMD) and Umbrella Sampling (US) via a flexible harmonic
restraint bias.
This fix interfaces LAMMPS to the collective variables `Colvars
<https://colvars.github.io>`_ library, which allows to accelerate sampling of
rare events and the computation of free energy surfaces and potentials of
mean force (PMFs) for any set of collective variables using a variety of
sampling methods (e.g. umbrella-sampling, metadynamics, ABF...).
This documentation describes only the ``fix colvars`` command itself in
a LAMMPS script. The Colvars library is documented via the included
`PDF manual <PDF/colvars-refman-lammps.pdf>`_ or at the webpage
This documentation describes only the "fix colvars" command itself in
a LAMMPS script. The Colvars library is fully documented in the included
`PDF manual <PDF/colvars-refman-lammps.pdf>`_ or in the webpage
`https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html
<https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html>`_.
The Colvars library is developed at `https://github.com/Colvars/colvars
<https://github.com/Colvars/colvars>`_ A detailed discussion of its
implementation is in :ref:`(Fiorin) <Fiorin>`; additional references are
printed at runtime based on specific features being used.
<https://github.com/colvars/colvars>`_ A detailed discussion of its
implementation is in :ref:`(Fiorin) <Fiorin>`; additional citations for
specific features are printed at runtime if these features are used.
There are some example scripts for using this package with LAMMPS in the
``examples/PACKAGES/colvars`` directory.
There are example scripts on the `Colvars website <https://colvars.github.io>`_
as well as in the ``examples/PACKAGES/colvars`` directory in the LAMMPS
source tree.
----------
The only required argument to ``fix colvars`` is the filename to the
Colvars configuration file that contains the definition of the variables
and any biasing methods applied to them. from the MD program in which
the colvars library has been integrated.
The only required argument to the fix is the name of the Colvars
configuration file. The contents of this file are independent from the MD
engine in which the Colvars library has been integrated, save for the units
that are specific to each engine. In LAMMPS, the units used by Colvars are
consistent with those specificed by the :doc:`units <units>` command.
The *group-ID* entry is ignored. ``fix colvars`` will always apply to
.. versionadded:: Colvars_2023-06-04 The special value "*none*"
(lowercase) initializes an empty Colvars module, which
allows loading configuration dynamically using
:doc:`fix_modify <fix_modify>` (see below).
The *group-ID* entry is ignored. "fix colvars" will always apply to
the entire system, but specific atoms will be selected based on
selection keywords in the Colvars configuration file or files. There is
no need to define multiple ``fix colvars`` instances and it is not
no need to define multiple "fix colvars" instances and it is not
allowed.
The *output* keyword allows to specify the prefix of output files
generated by Colvars, for example ``output.colvars.traj`` or
``output.pmf``.
The "output" keyword allows to specify the prefix of output files generated
by Colvars, for example "*output*.colvars.traj" or "output.pmf". Supplying
an empty string suppresses any file output from Colvars to file, except for
data saved into the LAMMPS :doc:`binary restart <restart>` files.
The *input* keyword allows to specify an optional state file that
contains the restart information needed to continue a previous
simulation state. Note, however, that ``fix colvars`` records its state
in :doc:`binary restart <restart>` files, so when using the
:doc:`read_restart <read_restart>` command, this is usually not needed.
The "input" keyword allows to specify an optional state file that contains
the restart information needed to continue a previous simulation state.
However, because "fix colvars" records its state in LAMMPS :doc:`binary
restart <restart>` files, this is usually not needed when using the
:doc:`read_restart <read_restart>` command.
The *seed* keyword contains the seed for the random number generator
used by Colvars.
The *unwrap* keyword controls whether wrapped or unwrapped coordinates
are passed to the Colvars library for calculation of the collective
variables and the resulting forces. The default is *yes*, i.e. to use
the image flags to reconstruct the absolute atom positions. Setting
this to *no* will use the current local coordinates that are wrapped
back into the simulation cell at each re-neighboring instead. For
information about when and how this affects results, please see
The *unwrap* keyword controls whether wrapped or unwrapped coordinates are
passed to the Colvars library for calculation of the collective variables and
the resulting forces. The default is *yes*, i.e. the image flags are used to
reconstruct the absolute atom positions. Setting this to *no* will use the
current local coordinates that are wrapped back into the simulation cell at
each re-neighboring step instead. For information about when and how this
affects results, please see
`https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html#sec:colvar_atom_groups_wrapping
<https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html#sec:colvar_atom_groups_wrapping>`_.
The *tstat* keyword can be either NULL or the label of a thermostatting
fix that thermostats all atoms in the fix colvars group. This will be
used to let Colvars know what is the current thermostat target
The *tstat* keyword can be either "NULL" or the label of a thermostatting
fix that thermostats all atoms in the fix colvars group. This will be
used to provide the colvars module with the current thermostat target
temperature.
Restart, fix_modify, output, run start/stop, minimize info
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
The *seed* keyword contains the seed for the random number generator
that will be used in the colvars module.
This fix writes the current status of the colvars module into
:doc:`binary restart files <restart>`. This is in addition to the text
mode ``.colvars.state`` written by Colvars itself and the information in
both files is identical.
The :doc:`fix_modify <fix_modify>` *energy* option is supported by this
fix to add the energy change from the biasing force added by Colvars to
the global potential energy of the system as part of :doc:`thermodynamic
output <thermo_style>`. The default setting for this fix is
:doc:`fix_modify energy no <fix_modify>`.
Restarting
""""""""""
The *fix_modify configfile <config file>* option loads Colvars
configuration from an additional file. This option can only be used,
after the system has been initialized with a :doc:`run <run>` command.
This fix writes the current state of the Colvars module into :doc:`binary
restart files <restart>`. This is in addition to the text-mode
".colvars.state" state file that is written by the Colvars module itself.
The information contained in both files is identical, and the binary LAMMPS
restart file is also used by fix colvars when :doc:`read_restart
<read_restart>` is called in a LAMMPS script. In that case, there is
typically no need to specify the *input* keyword.
The *fix_modify config <quoted string>* option allows to add settings
from inline strings. Those have to fit on a single line when enclosed in
a pair of double quotes ("), or can span multiple lines when bracketed
by a pair of triple double quotes (""", like Python embedded
documentation).
As long as LAMMPS binary restarts will be used to continue a simulation, it
is safe to delete the ".colvars.state" files to save space. However, when a
LAMMPS simulation is restarted using :doc:`read_data <read_data>`, the
Colvars state file must be available and loaded via the "input" keyword or
via a "fix_modify Colvars load" command (see below).
When restarting, the fix and the Colvars module should be created and
configured using either the original configuration file(s).
Output
""""""
This fix computes a global scalar which can be accessed by various
:doc:`output commands <Howto_output>`. The scalar is the Colvars energy
mentioned above. The scalar value calculated by this fix is
"extensive".
:doc:`output commands <Howto_output>`. The scalar is the energy due to all
external potentials defined in the Colvars configuration. The scalar value
calculated by this fix is "extensive".
Aside from the state information in a ".colvars.state" file, other
`output files <https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html#sec:colvars_output>`_
are produced by Colvars depending on the type of simulation.
For this reason, the "output" keyword is required for fix colvars.
Controlling Colvars via `fix_modify`
""""""""""""""""""""""""""""""""""""
The :doc:`fix_modify <fix_modify>` command may be used on "fix colvars" in
either one of two ways:
(1) Provide updated values for the fix parameters, such as *output*, *input*,
*unwrap*, *tstat* and *seed*. Additionally, the :doc:`fix_modify
<fix_modify>` *energy* keyword is supported by this fix to add the energy
change from the biasing force added by Colvars to the global potential
energy of the system as part of :doc:`thermodynamic output <thermo_style>`
(the default is :doc:`fix_modify energy no <fix_modify>`).
For example, in a multi-step LAMMPS script involving multiple thermostats
(e.g. fix nvt followed by fix npt), Colvars can read a new thermostat's
target temperature like this:
.. code-block:: LAMMPS
fix NVT all nvt ...
fix Colvars all colvars <configfile> output equil1 tstat NVT
run <NUMSTEPS>
unfix nvt
fix NPT all n ...
fix_modify Colvars tstat NPT
fix_modify Colvars output equil2
(2) .. versionadded:: Colvars_2023-06-04 Call one of the scripting
functions provided by the Colvars module itself (a full list is available
in the Colvars doc). The arguments to these functions are provided as
strings and passed to Colvars.
LAMMPS variables referenced by their string representation
"${variable}" will be expanded immediately. Note also that this
variable expansion *will also happen within quotes*, similar to what the
:doc:`mdi <mdi>` command provides. This feature makes it possible to use
the values of certain LAMMPS variables in Colvars configuration strings.
For example, to synchronize the LAMMPS and Colvars dump frequencies:
.. code-block:: LAMMPS
variable freq index 10000
dump myDump all atom/zstd ${freq} dump.atom.zstd
fix_modify Colvars config "colvarsTrajFrequency ${freq}"
.. note::
Although it is possible to use :doc:`fix_modify <fix_modify>` at any time,
its results will only reflect the state of the Colvars module at the end
of the most recent "run" or "minimize" command. Any new configuration
added via "fix_modify Colvars configfile" or "fix_modify Colvars config"
will only be loaded when the simulation resumes. Configuration files or
strings will be parsed in the same sequence as they were provided in the
LAMMPS script.
Restrictions
""""""""""""
``fix colvars`` is provided by the COLVARS package and is only available
if LAMMPS was built with that package. Some of the features also
require code available from the LEPTON package. See the :doc:`Build
This fix is provided by the COLVARS package and is only available if LAMMPS
was built with that package (default in most builds). Some of the features
also require code available from the LEPTON package. See the :doc:`Build
package <Build_package>` page for more info.
There can only be one Colvars instance defined at a time. Since the
interface communicates only the minimum amount of information and the
Colvars module itself can handle an arbitrary number of collective
interface communicates only the minimum required amount of information, and
the Colvars module itself can handle an arbitrary number of collective
variables, this is not a limitation of functionality.
Related commands
""""""""""""""""
:doc:`fix smd <fix_smd>`, :doc:`fix spring <fix_spring>`,
:doc:`fix plumed <fix_plumed>`
Default
"""""""
The default options are input = NULL, output = out, seed = 1966, unwrap yes,
and tstat = NULL.
----------
.. _Fiorin:
**(Fiorin)** Fiorin, Klein, Henin, Mol. Phys. 111, 3345 (2013) https://doi.org/10.1080/00268976.2013.813594
**(Fiorin)** Fiorin, Klein, Henin, Mol. Phys., DOI:10.1080/00268976.2013.813594
.. _Colvars_LAMMPS_doc:
https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html

View File

@ -9,78 +9,42 @@
// Colvars repository at GitHub.
#include "colvarproxy_lammps.h"
#include <mpi.h>
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include "lammps.h"
#include "error.h"
#include "output.h"
#include "utils.h"
#include "random_park.h"
#include "colvarmodule.h"
#include "colvarproxy.h"
#include <sstream>
#include "colvarproxy_lammps.h"
#include "colvarscript.h"
#define HASH_FAIL -1
colvarproxy_lammps::colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp,
const char *inp_name,
const char *out_name,
const int seed,
const double temp,
MPI_Comm root2root)
: _lmp(lmp), inter_comm(root2root)
colvarproxy_lammps::colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp)
: _lmp(lmp)
{
if (cvm::debug())
log("Initializing the colvars proxy object.\n");
_random = nullptr;
_random = new LAMMPS_NS::RanPark(lmp,seed);
engine_name_ = "LAMMPS";
first_timestep=true;
previous_step=-1;
do_exit=false;
first_timestep = true;
previous_step = -1;
do_exit = false;
inter_me = 0;
inter_num = 1;
engine_ready_ = false;
// set input restart name and strip the extension, if present
input_prefix_str = std::string(inp_name ? inp_name : "");
if (input_prefix_str.rfind(".colvars.state") != std::string::npos)
input_prefix_str.erase(input_prefix_str.rfind(".colvars.state"),
std::string(".colvars.state").size());
// output prefix is always given
output_prefix_str = std::string(out_name);
// not so for restarts
restart_output_prefix_str = std::string("rest");
// check if it is possible to save output configuration
if ((!output_prefix_str.size()) && (!restart_output_prefix_str.size())) {
error("Error: neither the final output state file or "
"the output restart file could be defined, exiting.\n");
}
// try to extract a restart prefix from a potential restart command.
LAMMPS_NS::Output *outp = _lmp->output;
if ((outp->restart_every_single > 0) && (outp->restart1 != nullptr)) {
restart_frequency_engine = outp->restart_every_single;
restart_output_prefix_str = std::string(outp->restart1);
} else if ((outp->restart_every_double > 0) && (outp->restart2a != nullptr)) {
restart_frequency_engine = outp->restart_every_double;
restart_output_prefix_str = std::string(outp->restart2a);
}
// trim off unwanted stuff from the restart prefix
if (restart_output_prefix_str.rfind(".*") != std::string::npos)
restart_output_prefix_str.erase(restart_output_prefix_str.rfind(".*"),2);
// initialize multi-replica support, if available
if (replica_enabled() == COLVARS_OK) {
MPI_Comm_rank(inter_comm, &inter_me);
MPI_Comm_size(inter_comm, &inter_num);
}
if (cvm::debug())
log("Done initializing the colvars proxy object.\n");
}
@ -91,63 +55,58 @@ void colvarproxy_lammps::init()
// create the colvarmodule instance
colvars = new colvarmodule(this);
// Create instance of scripting interface
script = new colvarscript(this, colvars);
cvm::log("Using LAMMPS interface, version "+
cvm::to_str(COLVARPROXY_VERSION)+".\n");
colvars->cite_feature("LAMMPS engine");
colvars->cite_feature("Colvars-LAMMPS interface");
my_angstrom = _lmp->force->angstrom;
// Front-end unit is the same as back-end
angstrom_value_ = my_angstrom;
// my_kcal_mol = _lmp->force->qe2f / 23.060549;
// force->qe2f is 1eV expressed in LAMMPS' energy unit (1 if unit is eV, 23 if kcal/mol)
angstrom_value_ = _lmp->force->angstrom;
boltzmann_ = _lmp->force->boltz;
my_timestep = _lmp->update->dt * _lmp->force->femtosecond;
set_integration_timestep(_lmp->update->dt * _lmp->force->femtosecond);
if (_lmp->update->ntimestep != 0) {
cvm::log("Setting initial step number from LAMMPS: "+
cvm::to_str(_lmp->update->ntimestep)+"\n");
colvarmodule::it = colvarmodule::it_restart =
static_cast<cvm::step_number>(_lmp->update->ntimestep);
}
if (cvm::debug()) {
cvm::log("atoms_ids = "+cvm::to_str(atoms_ids)+"\n");
cvm::log("atoms_refcount = "+cvm::to_str(atoms_refcount)+"\n");
cvm::log("atoms_positions = "+cvm::to_str(atoms_positions)+"\n");
cvm::log(cvm::line_marker);
cvm::log("Info: done initializing the colvars proxy object.\n");
colvars->set_initial_step(static_cast<cvm::step_number>(_lmp->update->ntimestep));
}
}
int colvarproxy_lammps::add_config_file(const char *conf_file)
{
return colvars->read_config_file(conf_file);
}
int colvarproxy_lammps::add_config_string(const std::string &conf)
{
return colvars->read_config_string(conf);
}
int colvarproxy_lammps::read_state_file(char const *state_filename)
{
input_prefix() = std::string(state_filename);
return colvars->setup_input();
}
colvarproxy_lammps::~colvarproxy_lammps()
{
delete _random;
if (_random) {
delete _random;
}
}
void colvarproxy_lammps::set_random_seed(int seed)
{
if (_random) {
delete _random;
}
_random = new LAMMPS_NS::RanPark(_lmp, seed);
}
void colvarproxy_lammps::set_replicas_communicator(MPI_Comm root2root)
{
inter_comm = root2root;
// initialize multi-replica support, if available
if (replica_enabled() == COLVARS_OK) {
MPI_Comm_rank(inter_comm, &inter_me);
MPI_Comm_size(inter_comm, &inter_num);
}
}
// re-initialize data where needed
int colvarproxy_lammps::setup()
{
int error_code = colvarproxy::setup();
my_timestep = _lmp->update->dt * _lmp->force->femtosecond;
set_integration_timestep(_lmp->update->dt * _lmp->force->femtosecond);
error_code |= colvars->update_engine_parameters();
error_code |= colvars->setup_input();
error_code |= colvars->setup_output();
@ -238,26 +197,6 @@ double colvarproxy_lammps::compute()
return bias_energy;
}
void colvarproxy_lammps::serialize_status(std::string &rst)
{
std::ostringstream os;
colvars->write_restart(os);
rst = os.str();
}
// set status from string
bool colvarproxy_lammps::deserialize_status(std::string &rst)
{
std::istringstream is;
is.str(rst);
if (!colvars->read_restart(is)) {
return false;
} else {
return true;
}
}
cvm::rvector colvarproxy_lammps::position_distance(cvm::atom_pos const &pos1,
cvm::atom_pos const &pos2)
@ -292,6 +231,24 @@ void colvarproxy_lammps::error(std::string const &message)
}
char const *colvarproxy_lammps::script_obj_to_str(unsigned char *obj)
{
// For now we assume that all objects passed by FixColvars are strings
return reinterpret_cast<char *>(obj);
}
std::vector<std::string> colvarproxy_lammps::script_obj_to_str_vector(unsigned char *obj)
{
if (cvm::debug()) {
cvm::log("Called colvarproxy_lammps::script_obj_to_str_vector().\n");
}
std::string const input(reinterpret_cast<char *>(obj));
return LAMMPS_NS::utils::split_words(input); // :-)))
}
int colvarproxy_lammps::set_unit_system(std::string const &units_in, bool /*check_only*/)
{
std::string lmp_units = _lmp->update->unit_style;

View File

@ -33,9 +33,8 @@ class colvarproxy_lammps : public colvarproxy {
LAMMPS_NS::RanPark *_random;
// state of LAMMPS properties
double my_timestep, my_angstrom;
double bias_energy;
int previous_step;
cvm::step_number previous_step;
bool first_timestep;
bool do_exit;
@ -47,10 +46,19 @@ class colvarproxy_lammps : public colvarproxy {
public:
friend class cvm::atom;
colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp, const char *, const char *, const int, const double,
MPI_Comm);
colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp);
~colvarproxy_lammps() override;
void init();
/// Set the internal seed used by \link rand_gaussian() \endlink
void set_random_seed(int seed);
/// Set the multiple replicas communicator
void set_replicas_communicator(MPI_Comm root2root);
int setup() override;
// disable default and copy constructor
@ -68,28 +76,14 @@ class colvarproxy_lammps : public colvarproxy {
// perform colvars computation. returns biasing energy
double compute();
// dump status to string
void serialize_status(std::string &);
// set status from string
bool deserialize_status(std::string &);
// read additional config from file
int add_config_file(char const *config_filename);
// read additional config from string
int add_config_string(const std::string &config);
// load a state file
int read_state_file(char const *state_filename);
// Request to set the units used internally by Colvars
int set_unit_system(std::string const &units_in, bool check_only) override;
inline cvm::real dt() override
{
return my_timestep;
}; // return _lmp->update->dt * _lmp->force->femtosecond; };
/// Convert a command-line argument to string
char const *script_obj_to_str(unsigned char *obj);
/// Convert a command-line argument to a vector of strings
std::vector<std::string> script_obj_to_str_vector(unsigned char *obj);
void add_energy(cvm::real energy) override { bias_energy += energy; };
void request_total_force(bool yesno) override { total_force_requested = yesno; };

View File

@ -1,3 +1,3 @@
#ifndef COLVARPROXY_VERSION
#define COLVARPROXY_VERSION "2023-04-12"
#define COLVARPROXY_VERSION "2024-07-05"
#endif

View File

@ -23,26 +23,33 @@
/* ----------------------------------------------------------------------
Contributing author: Axel Kohlmeyer (Temple U)
Currently maintained by: Giacomo Fiorin (NIH)
------------------------------------------------------------------------- */
#include "fix_colvars.h"
#include "inthash.h"
#include "atom.h"
#include "citeme.h"
#include "comm.h"
#include "domain.h"
#include "error.h"
#include "input.h"
#include "memory.h"
#include "modify.h"
#include "output.h"
#include "respa.h"
#include "universe.h"
#include "update.h"
#include <cstring>
#include <iostream>
#include <vector>
#include "colvarproxy_lammps.h"
#include "colvarmodule.h"
#include "colvarproxy.h"
#include "colvarproxy_lammps.h"
#include "colvarscript.h"
#include "colvars_memstream.h"
/* struct for packed data communication of coordinates and forces. */
@ -58,197 +65,11 @@ inline std::ostream & operator<< (std::ostream &out, const LAMMPS_NS::commdata &
return out;
}
/* re-usable integer hash table code with static linkage. */
/** hash table top level data structure */
typedef struct inthash_t {
struct inthash_node_t **bucket; /* array of hash nodes */
int size; /* size of the array */
int entries; /* number of entries in table */
int downshift; /* shift cound, used in hash function */
int mask; /* used to select bits for hashing */
} inthash_t;
/** hash table node data structure */
typedef struct inthash_node_t {
int data; /* data in hash node */
int key; /* key for hash lookup */
struct inthash_node_t *next; /* next node in hash chain */
} inthash_node_t;
#define HASH_FAIL -1
#define HASH_LIMIT 0.5
/* initialize new hash table */
static void inthash_init(inthash_t *tptr, int buckets);
/* lookup entry in hash table */
static int inthash_lookup(void *tptr, int key);
/* insert an entry into hash table. */
static int inthash_insert(inthash_t *tptr, int key, int data);
/* delete the hash table */
static void inthash_destroy(inthash_t *tptr);
/************************************************************************
* integer hash code:
************************************************************************/
/* inthash() - Hash function returns a hash number for a given key.
* tptr: Pointer to a hash table, key: The key to create a hash number for */
static int inthash(const inthash_t *tptr, int key) {
int hashvalue;
hashvalue = (((key*1103515249)>>tptr->downshift) & tptr->mask);
if (hashvalue < 0) {
hashvalue = 0;
}
return hashvalue;
}
/*
* rebuild_table_int() - Create new hash table when old one fills up.
*
* tptr: Pointer to a hash table
*/
static void rebuild_table_int(inthash_t *tptr) {
inthash_node_t **old_bucket, *old_hash, *tmp;
int old_size, h, i;
old_bucket=tptr->bucket;
old_size=tptr->size;
/* create a new table and rehash old buckets */
inthash_init(tptr, old_size<<1);
for (i=0; i<old_size; i++) {
old_hash=old_bucket[i];
while (old_hash) {
tmp=old_hash;
old_hash=old_hash->next;
h=inthash(tptr, tmp->key);
tmp->next=tptr->bucket[h];
tptr->bucket[h]=tmp;
tptr->entries++;
} /* while */
} /* for */
/* free memory used by old table */
free(old_bucket);
}
/*
* inthash_init() - Initialize a new hash table.
*
* tptr: Pointer to the hash table to initialize
* buckets: The number of initial buckets to create
*/
void inthash_init(inthash_t *tptr, int buckets) {
/* make sure we allocate something */
if (buckets==0)
buckets=16;
/* initialize the table */
tptr->entries=0;
tptr->size=2;
tptr->mask=1;
tptr->downshift=29;
/* ensure buckets is a power of 2 */
while (tptr->size<buckets) {
tptr->size<<=1;
tptr->mask=(tptr->mask<<1)+1;
tptr->downshift--;
} /* while */
/* allocate memory for table */
tptr->bucket=(inthash_node_t **) calloc(tptr->size, sizeof(inthash_node_t *));
}
/*
* inthash_lookup() - Lookup an entry in the hash table and return a pointer to
* it or HASH_FAIL if it wasn't found.
*
* tptr: Pointer to the hash table
* key: The key to lookup
*/
int inthash_lookup(void *ptr, int key) {
const inthash_t *tptr = (const inthash_t *) ptr;
int h;
inthash_node_t *node;
/* find the entry in the hash table */
h=inthash(tptr, key);
for (node=tptr->bucket[h]; node!=nullptr; node=node->next) {
if (node->key == key)
break;
}
/* return the entry if it exists, or HASH_FAIL */
return(node ? node->data : HASH_FAIL);
}
/*
* inthash_insert() - Insert an entry into the hash table. If the entry already
* exists return a pointer to it, otherwise return HASH_FAIL.
*
* tptr: A pointer to the hash table
* key: The key to insert into the hash table
* data: A pointer to the data to insert into the hash table
*/
int inthash_insert(inthash_t *tptr, int key, int data) {
int tmp;
inthash_node_t *node;
int h;
/* check to see if the entry exists */
if ((tmp=inthash_lookup(tptr, key)) != HASH_FAIL)
return(tmp);
/* expand the table if needed */
while (tptr->entries>=HASH_LIMIT*tptr->size)
rebuild_table_int(tptr);
/* insert the new entry */
h=inthash(tptr, key);
node=(struct inthash_node_t *) malloc(sizeof(inthash_node_t));
node->data=data;
node->key=key;
node->next=tptr->bucket[h];
tptr->bucket[h]=node;
tptr->entries++;
return HASH_FAIL;
}
/*
* inthash_destroy() - Delete the entire table, and all remaining entries.
*
*/
void inthash_destroy(inthash_t *tptr) {
inthash_node_t *node, *last;
int i;
for (i=0; i<tptr->size; i++) {
node = tptr->bucket[i];
while (node != nullptr) {
last = node;
node = node->next;
free(last);
}
}
/* free the entire array of buckets */
if (tptr->bucket != nullptr) {
free(tptr->bucket);
memset(tptr, 0, sizeof(inthash_t));
}
}
/***************************************************************/
using namespace LAMMPS_NS;
using namespace FixConst;
using namespace IntHash_NS;
// initialize static class members
int FixColvars::instances=0;
@ -287,72 +108,137 @@ FixColvars::FixColvars(LAMMPS *lmp, int narg, char **arg) :
me = comm->me;
root2root = MPI_COMM_NULL;
proxy = nullptr;
if (strcmp(arg[3], "none") == 0) {
conf_file = nullptr;
} else {
conf_file = utils::strdup(arg[3]);
}
conf_file = utils::strdup(arg[3]);
rng_seed = 1966;
unwrap_flag = 1;
inp_name = nullptr;
out_name = nullptr;
tmp_name = nullptr;
/* parse optional arguments */
int iarg = 4;
while (iarg < narg) {
// we have keyword/value pairs. check if value is missing
if (iarg+1 == narg)
error->all(FLERR,"Missing argument to keyword");
if (0 == strcmp(arg[iarg], "input")) {
inp_name = utils::strdup(arg[iarg+1]);
} else if (0 == strcmp(arg[iarg], "output")) {
out_name = utils::strdup(arg[iarg+1]);
} else if (0 == strcmp(arg[iarg], "seed")) {
rng_seed = utils::inumeric(FLERR,arg[iarg+1],false,lmp);
} else if (0 == strcmp(arg[iarg], "unwrap")) {
unwrap_flag = utils::logical(FLERR,arg[iarg+1],false,lmp);
} else if (0 == strcmp(arg[iarg], "tstat")) {
tmp_name = utils::strdup(arg[iarg+1]);
} else {
error->all(FLERR,"Unknown fix colvars parameter");
}
++iarg; ++iarg;
}
if (!out_name) out_name = utils::strdup("out");
tfix_name = nullptr;
/* initialize various state variables. */
tstat_fix = nullptr;
energy = 0.0;
nlevels_respa = 0;
init_flag = 0;
num_coords = 0;
comm_buf = nullptr;
taglist = nullptr;
force_buf = nullptr;
proxy = nullptr;
idmap = nullptr;
script_args[0] = reinterpret_cast<unsigned char *>(strdup("fix_modify"));
parse_fix_arguments(narg, arg, true);
if (!out_name) out_name = utils::strdup("out");
if (me == 0) {
#ifdef LAMMPS_BIGBIG
utils::logmesg(lmp, "colvars: Warning: cannot handle atom ids > 2147483647\n");
#endif
proxy = new colvarproxy_lammps(lmp);
proxy->init();
proxy->set_random_seed(rng_seed);
proxy->set_target_temperature(t_target);
if (conf_file) {
proxy->add_config("configfile", conf_file);
}
}
/* storage required to communicate a single coordinate or force. */
size_one = sizeof(struct commdata);
}
/*********************************
* Clean up on deleting the fix. *
*********************************/
int FixColvars::parse_fix_arguments(int narg, char **arg, bool fix_constructor)
{
int const iarg_start = fix_constructor ? 4 : 0;
int iarg = iarg_start;
while (iarg < narg) {
bool is_fix_keyword = false;
if (0 == strcmp(arg[iarg], "input")) {
inp_name = utils::strdup(arg[iarg+1]);
// input prefix is set in FixColvars::setup()
is_fix_keyword = true;
} else if (0 == strcmp(arg[iarg], "output")) {
out_name = utils::strdup(arg[iarg+1]);
// output prefix is set in FixColvars::setup()
is_fix_keyword = true;
} else if (0 == strcmp(arg[iarg], "seed")) {
rng_seed = utils::inumeric(FLERR, arg[iarg+1], false, lmp);
is_fix_keyword = true;
} else if (0 == strcmp(arg[iarg], "unwrap")) {
unwrap_flag = utils::logical(FLERR, arg[iarg+1], false, lmp);
is_fix_keyword = true;
} else if (0 == strcmp(arg[iarg], "tstat")) {
tfix_name = utils::strdup(arg[iarg+1]);
if (me == 0) set_thermostat_temperature();
is_fix_keyword = true;
}
if (is_fix_keyword) {
// Valid LAMMPS fix keyword: raise error if it has no argument
if (iarg + 1 == narg) {
if (fix_constructor) {
error->all(FLERR, ("Missing argument to keyword \""+
std::string(arg[iarg]) +"\""));
} else {
// Error code consistent with Fix::modify_param()
return 0;
}
}
} else {
if (fix_constructor) {
error->all(FLERR, "Unrecognized fix colvars argument: please note that "
"Colvars script commands are not allowed until after the "
"fix is created");
} else {
if (iarg > iarg_start) {
error->all(FLERR,
"Unrecognized fix colvars argument: please note that "
"you cannot combine fix colvars keywords and Colvars "
"script commands in the same line");
} else {
// Return negative error code to try the Colvars script commands
return -1;
}
}
}
iarg += 2;
}
return iarg;
}
FixColvars::~FixColvars()
{
delete[] conf_file;
delete[] inp_name;
delete[] out_name;
delete[] tmp_name;
delete[] tfix_name;
memory->sfree(comm_buf);
if (proxy) {
delete proxy;
inthash_t *hashtable = (inthash_t *)idmap;
inthash_destroy(hashtable);
delete hashtable;
}
if (idmap) {
inthash_destroy(idmap);
delete idmap;
}
if (root2root != MPI_COMM_NULL)
@ -374,31 +260,20 @@ int FixColvars::setmask()
return mask;
}
/* ---------------------------------------------------------------------- */
// initial checks for colvars run.
void FixColvars::init()
{
if (atom->tag_enable == 0)
error->all(FLERR,"Cannot use fix colvars without atom IDs");
error->all(FLERR, "Cannot use fix colvars without atom IDs");
if (atom->map_style == Atom::MAP_NONE)
error->all(FLERR,"Fix colvars requires an atom map, see atom_modify");
error->all(FLERR, "Fix colvars requires an atom map, see atom_modify");
if ((me == 0) && (update->whichflag == 2))
error->warning(FLERR,"Using fix colvars with minimization");
error->warning(FLERR, "Using fix colvars with minimization");
if (utils::strmatch(update->integrate_style,"^respa"))
if (utils::strmatch(update->integrate_style, "^respa"))
nlevels_respa = ((Respa *) update->integrate)->nlevels;
}
/* ---------------------------------------------------------------------- */
void FixColvars::one_time_init()
{
int i,tmp;
if (init_flag) return;
init_flag = 1;
@ -406,103 +281,188 @@ void FixColvars::one_time_init()
if (universe->nworlds > 1) {
// create inter root communicator
int color = 1;
if (me == 0) color = 0;
MPI_Comm_split(universe->uworld,color,universe->iworld,&root2root);
if (me == 0) {
color = 0;
}
MPI_Comm_split(universe->uworld, color, universe->iworld, &root2root);
if (me == 0) {
proxy->set_replicas_communicator(root2root);
}
}
}
// create and initialize the colvars proxy
void FixColvars::set_thermostat_temperature()
{
if (me == 0) {
utils::logmesg(lmp,"colvars: Creating proxy instance\n");
#ifdef LAMMPS_BIGBIG
utils::logmesg(lmp,"colvars: cannot handle atom ids > 2147483647\n");
#endif
if (inp_name) {
if (strcmp(inp_name,"NULL") == 0) {
delete[] inp_name;
inp_name = nullptr;
if (tfix_name) {
if (strcmp(tfix_name, "NULL") != 0) {
Fix *tstat_fix = modify->get_fix_by_id(tfix_name);
if (!tstat_fix) {
error->one(FLERR, "Could not find thermostat fix ID {}", tfix_name);
}
int tmp = 0;
double *tt = reinterpret_cast<double *>(tstat_fix->extract("t_target",
tmp));
if (tt) {
t_target = *tt;
} else {
error->one(FLERR, "Fix ID {} is not a thermostat fix", tfix_name);
}
}
}
// try to determine thermostat target temperature
double t_target = 0.0;
if (tmp_name) {
if (strcmp(tmp_name,"NULL") == 0) {
tstat_fix = nullptr;
} else {
tstat_fix = modify->get_fix_by_id(tmp_name);
if (!tstat_fix) error->one(FLERR, "Could not find thermostat fix ID {}", tmp_name);
double *tt = (double*) tstat_fix->extract("t_target", tmp);
if (tt) t_target = *tt;
else error->one(FLERR, "Fix ID {} is not a thermostat fix", tmp_name);
}
}
proxy = new colvarproxy_lammps(lmp,inp_name,out_name,rng_seed,t_target,root2root);
proxy->init();
proxy->add_config("configfile", conf_file);
proxy->parse_module_config();
num_coords = (proxy->modify_atom_positions()->size());
}
}
// send the list of all colvar atom IDs to all nodes.
// also initialize and build hashtable on master.
/* ---------------------------------------------------------------------- */
MPI_Bcast(&num_coords, 1, MPI_INT, 0, world);
memory->create(taglist,num_coords,"colvars:taglist");
memory->create(force_buf,3*num_coords,"colvars:force_buf");
void FixColvars::init_taglist()
{
int new_taglist_size = -1;
if (me == 0) {
// Number of atoms requested by Colvars
num_coords = static_cast<int>(proxy->modify_atom_positions()->size());
if (proxy->modified_atom_list()) {
new_taglist_size = num_coords;
proxy->reset_modified_atom_list();
} else {
new_taglist_size = -1;
}
}
// Broadcast number of colvar atoms; negative means no updates
MPI_Bcast(&new_taglist_size, 1, MPI_INT, 0, world);
if (new_taglist_size < 0) {
return;
}
num_coords = new_taglist_size;
if (taglist) {
memory->destroy(taglist);
memory->destroy(force_buf);
}
memory->create(taglist, num_coords, "colvars:taglist");
memory->create(force_buf, 3*num_coords, "colvars:force_buf");
if (me == 0) {
// Initialize and build hashtable on MPI rank 0
std::vector<int> const &tl = *(proxy->get_atom_ids());
inthash_t *hashtable=new inthash_t;
inthash_init(hashtable, num_coords);
idmap = (void *)hashtable;
for (i=0; i < num_coords; ++i) {
if (idmap) {
delete idmap;
idmap = nullptr;
}
idmap = new inthash_t;
inthash_init(idmap, num_coords);
for (int i = 0; i < num_coords; ++i) {
taglist[i] = tl[i];
inthash_insert(hashtable, tl[i], i);
inthash_insert(idmap, tl[i], i);
}
}
// Broadcast colvar atom ID list
MPI_Bcast(taglist, num_coords, MPI_LMP_TAGINT, 0, world);
}
/* ---------------------------------------------------------------------- */
int FixColvars::modify_param(int narg, char **arg)
{
if (strcmp(arg[0],"configfile") == 0) {
if (narg < 2) error->all(FLERR,"Illegal fix_modify command");
if (me == 0) {
if (! proxy)
error->one(FLERR,"Cannot use fix_modify before initialization");
return proxy->add_config_file(arg[1]) == COLVARS_OK ? 2 : 0;
}
return 2;
} else if (strcmp(arg[0],"config") == 0) {
if (narg < 2) error->all(FLERR,"Illegal fix_modify command");
if (me == 0) {
if (! proxy)
error->one(FLERR,"Cannot use fix_modify before initialization");
std::string const conf(arg[1]);
return proxy->add_config_string(conf) == COLVARS_OK ? 2 : 0;
}
return 2;
} else if (strcmp(arg[0],"load") == 0) {
if (narg < 2) error->all(FLERR,"Illegal fix_modify command");
if (me == 0) {
if (! proxy)
error->one(FLERR,"Cannot use fix_modify before initialization");
return proxy->read_state_file(arg[1]) == COLVARS_OK ? 2 : 0;
}
if (narg > 100) {
error->one(FLERR, "Too many arguments for fix_modify command");
return 2;
}
// Parse arguments to fix colvars
int return_code = parse_fix_arguments(narg, arg, false);
if (return_code >= 0) {
// A fix colvars argument was detected, return directly
return return_code;
}
// Any unknown arguments will go through the Colvars scripting interface
if (me == 0) {
int error_code = COLVARSCRIPT_OK;
colvarscript *script = proxy->script;
script->set_cmdline_main_cmd("fix_modify " + std::string(id));
for (int i = 0; i < narg; i++) {
// Substitute LAMMPS variables
// See https://github.com/lammps/lammps/commit/f9be11ac8ab460edff3709d66734d3fc2cd806dd
char *new_arg = arg[i];
int ncopy = strlen(new_arg) + 1;
char *copy = utils::strdup(new_arg);
char *work = new char[ncopy];
int nwork = ncopy;
lmp->input->substitute(copy,work,ncopy,nwork,0);
delete[] work;
new_arg = copy;
script_args[i+1] = reinterpret_cast<unsigned char *>(new_arg);
}
// Run the command through Colvars
error_code |= script->run(narg+1, script_args);
std::string const result = proxy->get_error_msgs() + script->str_result();
if (result.size()) {
std::istringstream is(result);
std::string line;
while (std::getline(is, line)) {
if (lmp->screen) fprintf(lmp->screen, "%s\n", line.c_str());
if (lmp->logfile) fprintf(lmp->logfile, "%s\n", line.c_str());
}
}
return (error_code == COLVARSCRIPT_OK) ? narg : 0;
} else {
// Return without error, don't block Fix::modify_params()
return narg;
}
return 0;
}
/* ---------------------------------------------------------------------- */
void FixColvars::setup_io()
{
if (me == 0) {
proxy->set_input_prefix(std::string(inp_name ? inp_name : ""));
if (proxy->input_prefix().size() > 0) {
proxy->log("Will read input state from file \""+
proxy->input_prefix()+".colvars.state\"");
}
proxy->set_output_prefix(std::string(out_name ? out_name : ""));
// Try to extract a restart prefix from a potential restart command
LAMMPS_NS::Output *outp = lmp->output;
if ((outp->restart_every_single > 0) &&
(outp->restart1 != nullptr)) {
proxy->set_default_restart_frequency(outp->restart_every_single);
proxy->set_restart_output_prefix(std::string(outp->restart1));
} else if ((outp->restart_every_double > 0) &&
(outp->restart2a != nullptr)) {
proxy->set_default_restart_frequency(outp->restart_every_double);
proxy->set_restart_output_prefix(std::string(outp->restart2a));
}
}
}
void FixColvars::setup(int vflag)
{
@ -514,7 +474,12 @@ void FixColvars::setup(int vflag)
MPI_Status status;
MPI_Request request;
one_time_init();
if (me == 0) {
setup_io();
proxy->parse_module_config();
}
init_taglist();
// determine size of comm buffer
nme=0;
@ -675,19 +640,6 @@ void FixColvars::post_force(int /*vflag*/)
if (me == 0) {
if (proxy->want_exit())
error->one(FLERR,"Run aborted on request from colvars module.\n");
if (!tstat_fix) {
proxy->set_target_temperature(0.0);
} else {
int tmp;
// get thermostat target temperature from corresponding fix,
// if the fix supports extraction.
double *tt = (double *) tstat_fix->extract("t_target", tmp);
if (tt)
proxy->set_target_temperature(*tt);
else
proxy->set_target_temperature(0.0);
}
}
const tagint * const tag = atom->tag;
@ -935,13 +887,17 @@ void FixColvars::end_of_step()
void FixColvars::write_restart(FILE *fp)
{
if (me == 0) {
std::string rest_text;
proxy->serialize_status(rest_text);
// TODO call write_output_files()
const char *cvm_state = rest_text.c_str();
int len = strlen(cvm_state) + 1; // need to include terminating null byte.
fwrite(&len,sizeof(int),1,fp);
fwrite(cvm_state,1,len,fp);
cvm::memory_stream ms;
if (proxy->colvars->write_state(ms)) {
int len_cv_state = ms.length();
// Will write the buffer's length twice, so that the fix can read it later, too
int len = len_cv_state + sizeof(int);
fwrite(&len, sizeof(int), 1, fp);
fwrite(&len, sizeof(int), 1, fp);
fwrite(ms.output_buffer(), 1, len_cv_state, fp);
} else {
error->all(FLERR, "Failed to write Colvars state to binary file");
}
}
}
@ -949,11 +905,13 @@ void FixColvars::write_restart(FILE *fp)
void FixColvars::restart(char *buf)
{
one_time_init();
if (me == 0) {
std::string rest_text(buf);
proxy->deserialize_status(rest_text);
// Read the buffer's length, then load it into Colvars starting right past that location
int length = *(reinterpret_cast<int *>(buf));
unsigned char *colvars_state_buffer = reinterpret_cast<unsigned char *>(buf + sizeof(int));
if (proxy->colvars->set_input_state_buffer(length, colvars_state_buffer) != COLVARS_OK) {
error->all(FLERR, "Failed to set the Colvars input state from string buffer");
}
}
}

View File

@ -21,7 +21,8 @@
------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------
Contributing author: Axel Kohlmeyer (Temple U)
Contributing author: Axel Kohlmeyer (Temple U)
Currently maintained by: Giacomo Fiorin (NIH)
------------------------------------------------------------------------- */
#ifdef FIX_CLASS
@ -35,6 +36,10 @@ FixStyle(colvars,FixColvars);
#include "fix.h"
// Forward declarations
namespace IntHash_NS {
class inthash_t;
}
class colvarproxy_lammps;
namespace LAMMPS_NS {
@ -66,9 +71,9 @@ class FixColvars : public Fix {
char *conf_file; // name of colvars config file
char *inp_name; // name/prefix of colvars restart file
char *out_name; // prefix string for all output files
char *tmp_name; // name of thermostat fix.
char *tfix_name; // name of thermostat fix.
int rng_seed; // seed to initialize random number generator
Fix *tstat_fix; // pointer to thermostat fix
double t_target = 0.0; // thermostat target temperature
double energy; // biasing energy of the fix
int me; // my MPI rank in this "world".
@ -80,8 +85,10 @@ class FixColvars : public Fix {
struct commdata *comm_buf; // communication buffer
double *force_buf; // communication buffer
void *idmap; // hash for mapping atom indices to consistent order.
int *rev_idmap; // list of the hash keys for reverse mapping.
/// Arguments passed from fix_modify to the Colvars script interface
unsigned char *script_args[100];
IntHash_NS::inthash_t *idmap; // hash for mapping atom indices to consistent order.
int nlevels_respa; // flag to determine respa levels.
int store_forces; // flag to determine whether to store total forces
@ -90,7 +97,20 @@ class FixColvars : public Fix {
static int instances; // count fix instances, since colvars currently
// only supports one instance at a time
MPI_Comm root2root; // inter-root communicator for multi-replica support
void one_time_init(); // one time initialization
void init_taglist(); // initialize list of atom tags and hash table
/// Share with Colvars the thermostat fix named by tfix_name
void set_thermostat_temperature();
/// Tell Colvars where to get its state from and where to save it
void setup_io();
/// Parse LAMMPS-specific arguments to either fix or fix_modify
/// \param narg Number of arguments
/// \param arg Array of strings
/// \param fix_constructor If false, try Colvars commands if LAMMPS ones fail
int parse_fix_arguments(int narg, char **arg, bool fix_constructor = true);
};
} // namespace LAMMPS_NS

169
src/COLVARS/inthash.cpp Normal file
View File

@ -0,0 +1,169 @@
// clang-format off
// -*- c++ -*-
#include <cstdlib>
#include <cstring>
#include <memory>
#include "inthash.h"
namespace IntHash_NS {
/************************************************************************
* integer hash code:
************************************************************************/
/* inthash() - Hash function returns a hash number for a given key.
* tptr: Pointer to a hash table, key: The key to create a hash number for */
int inthash(const inthash_t *tptr, int key) {
int hashvalue;
hashvalue = (((key*1103515249)>>tptr->downshift) & tptr->mask);
if (hashvalue < 0) {
hashvalue = 0;
}
return hashvalue;
}
/*
* rebuild_table_int() - Create new hash table when old one fills up.
*
* tptr: Pointer to a hash table
*/
void rebuild_table_int(inthash_t *tptr) {
inthash_node_t **old_bucket, *old_hash, *tmp;
int old_size, h, i;
old_bucket=tptr->bucket;
old_size=tptr->size;
/* create a new table and rehash old buckets */
inthash_init(tptr, old_size<<1);
for (i=0; i<old_size; i++) {
old_hash=old_bucket[i];
while (old_hash) {
tmp=old_hash;
old_hash=old_hash->next;
h=inthash(tptr, tmp->key);
tmp->next=tptr->bucket[h];
tptr->bucket[h]=tmp;
tptr->entries++;
} /* while */
} /* for */
/* free memory used by old table */
free(old_bucket);
}
/*
* inthash_init() - Initialize a new hash table.
*
* tptr: Pointer to the hash table to initialize
* buckets: The number of initial buckets to create
*/
void inthash_init(inthash_t *tptr, int buckets) {
/* make sure we allocate something */
if (buckets==0)
buckets=16;
/* initialize the table */
tptr->entries=0;
tptr->size=2;
tptr->mask=1;
tptr->downshift=29;
/* ensure buckets is a power of 2 */
while (tptr->size<buckets) {
tptr->size<<=1;
tptr->mask=(tptr->mask<<1)+1;
tptr->downshift--;
} /* while */
/* allocate memory for table */
tptr->bucket=(inthash_node_t **) calloc(tptr->size, sizeof(inthash_node_t *));
}
/*
* inthash_lookup() - Lookup an entry in the hash table and return a pointer to
* it or HASH_FAIL if it wasn't found.
*
* tptr: Pointer to the hash table
* key: The key to lookup
*/
int inthash_lookup(inthash_t *tptr, int key) {
int h;
inthash_node_t *node;
/* find the entry in the hash table */
h=inthash(tptr, key);
for (node=tptr->bucket[h]; node!=nullptr; node=node->next) {
if (node->key == key)
break;
}
/* return the entry if it exists, or HASH_FAIL */
return(node ? node->data : HASH_FAIL);
}
/*
* inthash_insert() - Insert an entry into the hash table. If the entry already
* exists return a pointer to it, otherwise return HASH_FAIL.
*
* tptr: A pointer to the hash table
* key: The key to insert into the hash table
* data: A pointer to the data to insert into the hash table
*/
int inthash_insert(inthash_t *tptr, int key, int data) {
int tmp;
inthash_node_t *node;
int h;
/* check to see if the entry exists */
if ((tmp=inthash_lookup(tptr, key)) != HASH_FAIL)
return(tmp);
/* expand the table if needed */
while (tptr->entries>=HASH_LIMIT*tptr->size)
rebuild_table_int(tptr);
/* insert the new entry */
h=inthash(tptr, key);
node=(struct inthash_node_t *) malloc(sizeof(inthash_node_t));
node->data=data;
node->key=key;
node->next=tptr->bucket[h];
tptr->bucket[h]=node;
tptr->entries++;
return HASH_FAIL;
}
/*
* inthash_destroy() - Delete the entire table, and all remaining entries.
*
*/
void inthash_destroy(inthash_t *tptr) {
inthash_node_t *node, *last;
int i;
for (i=0; i<tptr->size; i++) {
node = tptr->bucket[i];
while (node != nullptr) {
last = node;
node = node->next;
free(last);
}
}
/* free the entire array of buckets */
if (tptr->bucket != nullptr) {
free(tptr->bucket);
memset(tptr, 0, sizeof(inthash_t));
}
}
}

38
src/COLVARS/inthash.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef INTHASH_H
#define INTHASH_H
namespace IntHash_NS {
/* re-usable integer hash table code. */
/** hash table top level data structure */
typedef struct inthash_t {
struct inthash_node_t **bucket; /* array of hash nodes */
int size; /* size of the array */
int entries; /* number of entries in table */
int downshift; /* shift cound, used in hash function */
int mask; /* used to select bits for hashing */
} inthash_t;
/** hash table node data structure */
typedef struct inthash_node_t {
int data; /* data in hash node */
int key; /* key for hash lookup */
struct inthash_node_t *next; /* next node in hash chain */
} inthash_node_t;
#define HASH_FAIL -1
#define HASH_LIMIT 0.5
/* initialize new hash table */
void inthash_init(inthash_t *tptr, int buckets);
/* lookup entry in hash table */
int inthash_lookup(inthash_t *tptr, int key);
/* insert an entry into hash table. */
int inthash_insert(inthash_t *tptr, int key, int data);
/* delete the hash table */
void inthash_destroy(inthash_t *tptr);
} // namespace IntHash_NS
#endif