diff --git a/doc/src/fix_colvars.rst b/doc/src/fix_colvars.rst index 5acd79ba34..936fdaa83b 100644 --- a/doc/src/fix_colvars.rst +++ b/doc/src/fix_colvars.rst @@ -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 ` 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 ` 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 +`_ 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 `_ 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 `_ or in the webpage `https://colvars.github.io/colvars-refman-lammps/colvars-refman-lammps.html `_. The Colvars library is developed at `https://github.com/Colvars/colvars -`_ A detailed discussion of its -implementation is in :ref:`(Fiorin) `; additional references are -printed at runtime based on specific features being used. +`_ A detailed discussion of its +implementation is in :ref:`(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 `_ +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 ` 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 ` (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 ` 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 ` files, so when using the -:doc:`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 ` files, this is usually not needed when using the +:doc:`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 `_. -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 `. 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 ` *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 `. The default setting for this fix is -:doc:`fix_modify energy no `. +Restarting +"""""""""" -The *fix_modify configfile * option loads Colvars -configuration from an additional file. This option can only be used, -after the system has been initialized with a :doc:`run ` command. +This fix writes the current state of the Colvars module into :doc:`binary +restart files `. 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 +` is called in a LAMMPS script. In that case, there is +typically no need to specify the *input* keyword. -The *fix_modify config * 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 `, 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 `. The scalar is the Colvars energy -mentioned above. The scalar value calculated by this fix is -"extensive". +:doc:`output commands `. 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 `_ +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 ` 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 + ` *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 ` + (the default is :doc:`fix_modify energy no `). + 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 output equil1 tstat NVT + run + 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 ` 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 ` 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 ` 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 `, :doc:`fix spring `, :doc:`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 diff --git a/src/COLVARS/colvarproxy_lammps.cpp b/src/COLVARS/colvarproxy_lammps.cpp index 1c5d84d62a..37fe4ab0a6 100644 --- a/src/COLVARS/colvarproxy_lammps.cpp +++ b/src/COLVARS/colvarproxy_lammps.cpp @@ -9,78 +9,42 @@ // Colvars repository at GitHub. -#include "colvarproxy_lammps.h" +#include +#include +#include +#include +#include +#include +#include #include "lammps.h" #include "error.h" -#include "output.h" +#include "utils.h" #include "random_park.h" #include "colvarmodule.h" #include "colvarproxy.h" - -#include +#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(_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(_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(obj); +} + + +std::vector 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(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; diff --git a/src/COLVARS/colvarproxy_lammps.h b/src/COLVARS/colvarproxy_lammps.h index 0fc9f1ba12..80939a1c1d 100644 --- a/src/COLVARS/colvarproxy_lammps.h +++ b/src/COLVARS/colvarproxy_lammps.h @@ -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 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; }; diff --git a/src/COLVARS/colvarproxy_lammps_version.h b/src/COLVARS/colvarproxy_lammps_version.h index 4228740554..5901044b1e 100644 --- a/src/COLVARS/colvarproxy_lammps_version.h +++ b/src/COLVARS/colvarproxy_lammps_version.h @@ -1,3 +1,3 @@ #ifndef COLVARPROXY_VERSION -#define COLVARPROXY_VERSION "2023-04-12" +#define COLVARPROXY_VERSION "2024-07-05" #endif diff --git a/src/COLVARS/fix_colvars.cpp b/src/COLVARS/fix_colvars.cpp index 0b496ee71b..d2da1416d7 100644 --- a/src/COLVARS/fix_colvars.cpp +++ b/src/COLVARS/fix_colvars.cpp @@ -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 #include +#include -#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; inext; - 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->sizesize<<=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; isize; 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(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(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(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 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(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(buf)); + unsigned char *colvars_state_buffer = reinterpret_cast(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"); + } } } diff --git a/src/COLVARS/fix_colvars.h b/src/COLVARS/fix_colvars.h index 45fe09af78..550ca99d9e 100644 --- a/src/COLVARS/fix_colvars.h +++ b/src/COLVARS/fix_colvars.h @@ -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 diff --git a/src/COLVARS/inthash.cpp b/src/COLVARS/inthash.cpp new file mode 100644 index 0000000000..0ae349f839 --- /dev/null +++ b/src/COLVARS/inthash.cpp @@ -0,0 +1,169 @@ +// clang-format off +// -*- c++ -*- + +#include +#include +#include + +#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; inext; + 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->sizesize<<=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; isize; 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)); + } +} + +} diff --git a/src/COLVARS/inthash.h b/src/COLVARS/inthash.h new file mode 100644 index 0000000000..6427f0acd9 --- /dev/null +++ b/src/COLVARS/inthash.h @@ -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