From 5b7479aa28b2d216175cb395344bccbeacba0191 Mon Sep 17 00:00:00 2001 From: Viveca Lindahl Date: Mon, 28 Mar 2016 14:27:33 +0200 Subject: [PATCH] Add AWH biasing module + tests The AWH (Accelerated weight histogram) method is an adaptive biasing method used for overcoming free energy barriers and calculating free energies (see http://dx.doi.org/10.1063/1.4890371). Although AWH can in general bias any system parameter, this change only implements biasing of reaction coordinates. The actual force distribution and coordinate handling is taken care of by the pull code. AWH interacts with the pull code by registering itself as the external potential module for the coordinate that should be AWH biased. The AWH code sets the potential and force for those coordinates. The Grid test checks that the neighborhoods are correct. The Bias tests check the force, bias and free energy values for the final and initial phase, with MC and convolved force and without and with skipping updates. Change-Id: I202f58f7042e8e63c9d708fdcaca6da7e8a4022e --- docs/doxygen/cycle-suppressions.txt | 1 + docs/doxygen/lib/awh.md | 70 + docs/doxygen/user/mainpage.md | 3 + docs/user-guide/mdp-options.rst | 277 ++++ src/gromacs/CMakeLists.txt | 1 + src/gromacs/awh/CMakeLists.txt | 40 + src/gromacs/awh/awh.cpp | 344 +++++ src/gromacs/awh/awh.h | 234 +++ src/gromacs/awh/bias.cpp | 255 ++++ src/gromacs/awh/bias.h | 313 ++++ src/gromacs/awh/biasparams.cpp | 310 ++++ src/gromacs/awh/biasparams.h | 217 +++ src/gromacs/awh/biassharing.cpp | 152 ++ src/gromacs/awh/biassharing.h | 84 + src/gromacs/awh/biasstate.cpp | 1613 ++++++++++++++++++++ src/gromacs/awh/biasstate.h | 533 +++++++ src/gromacs/awh/coordstate.cpp | 170 +++ src/gromacs/awh/coordstate.h | 145 ++ src/gromacs/awh/dimparams.h | 116 ++ src/gromacs/awh/grid.cpp | 835 ++++++++++ src/gromacs/awh/grid.h | 389 +++++ src/gromacs/awh/histogramsize.cpp | 287 ++++ src/gromacs/awh/histogramsize.h | 198 +++ src/gromacs/awh/pointstate.cpp | 78 + src/gromacs/awh/pointstate.h | 521 +++++++ src/gromacs/awh/read-params.cpp | 801 ++++++++++ src/gromacs/awh/read-params.h | 98 ++ src/gromacs/awh/tests/CMakeLists.txt | 36 + src/gromacs/awh/tests/bias.cpp | 341 +++++ src/gromacs/awh/tests/biasstate.cpp | 184 +++ src/gromacs/awh/tests/grid.cpp | 185 +++ src/gromacs/awh/tests/pmf_target_format0.xvg | 15 + src/gromacs/awh/tests/pmf_target_format1.xvg | 15 + .../WithParameters_BiasTest_ForcesBiasPmf_0.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_1.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_2.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_3.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_4.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_5.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_6.xml | 130 ++ .../WithParameters_BiasTest_ForcesBiasPmf_7.xml | 130 ++ src/gromacs/fileio/checkpoint.cpp | 237 ++- src/gromacs/fileio/tpxio.cpp | 80 + src/gromacs/gmxpreprocess/grompp.cpp | 7 + src/gromacs/gmxpreprocess/readir.cpp | 17 + src/gromacs/gmxpreprocess/readpull.cpp | 2 +- .../tests/refdata/GetIrTest_EmptyInputWorks.xml | 3 + .../GetIrTest_HandlesDifferentKindsOfMdpLines.xml | 3 + .../refdata/GetIrTest_HandlesOnlyCutoffScheme.xml | 3 + .../GetIrTest_ProducesOutputFromElectricField.xml | 3 + ..._ProducesOutputFromElectricFieldOscillating.xml | 3 + ...rTest_ProducesOutputFromElectricFieldPulsed.xml | 3 + .../GetIrTest_UserErrorsSilentlyTolerated.xml | 3 + src/gromacs/mdlib/broadcaststructs.cpp | 27 + src/gromacs/mdlib/sim_util.cpp | 18 +- src/gromacs/mdtypes/awh-history.h | 133 ++ src/gromacs/mdtypes/awh-params.h | 145 ++ src/gromacs/mdtypes/forceoutput.h | 8 +- src/gromacs/mdtypes/inputrec.cpp | 141 ++ src/gromacs/mdtypes/inputrec.h | 15 +- src/gromacs/mdtypes/state.cpp | 2 + src/gromacs/mdtypes/state.h | 17 +- src/gromacs/pulling/pull.cpp | 31 +- src/gromacs/pulling/pull.h | 39 +- src/gromacs/random/seed.h | 5 +- src/gromacs/timing/wallcycle.cpp | 2 +- src/gromacs/timing/wallcycle.h | 2 +- src/gromacs/utility/pleasecite.cpp | 5 + src/programs/mdrun/md.cpp | 43 +- src/programs/mdrun/runner.h | 1 + 70 files changed, 10832 insertions(+), 67 deletions(-) create mode 100644 docs/doxygen/lib/awh.md create mode 100644 src/gromacs/awh/CMakeLists.txt create mode 100644 src/gromacs/awh/awh.cpp create mode 100644 src/gromacs/awh/awh.h create mode 100644 src/gromacs/awh/bias.cpp create mode 100644 src/gromacs/awh/bias.h create mode 100644 src/gromacs/awh/biasparams.cpp create mode 100644 src/gromacs/awh/biasparams.h create mode 100644 src/gromacs/awh/biassharing.cpp create mode 100644 src/gromacs/awh/biassharing.h create mode 100644 src/gromacs/awh/biasstate.cpp create mode 100644 src/gromacs/awh/biasstate.h create mode 100644 src/gromacs/awh/coordstate.cpp create mode 100644 src/gromacs/awh/coordstate.h create mode 100644 src/gromacs/awh/dimparams.h create mode 100644 src/gromacs/awh/grid.cpp create mode 100644 src/gromacs/awh/grid.h create mode 100644 src/gromacs/awh/histogramsize.cpp create mode 100644 src/gromacs/awh/histogramsize.h create mode 100644 src/gromacs/awh/pointstate.cpp create mode 100644 src/gromacs/awh/pointstate.h create mode 100644 src/gromacs/awh/read-params.cpp create mode 100644 src/gromacs/awh/read-params.h create mode 100644 src/gromacs/awh/tests/CMakeLists.txt create mode 100644 src/gromacs/awh/tests/bias.cpp create mode 100644 src/gromacs/awh/tests/biasstate.cpp create mode 100644 src/gromacs/awh/tests/grid.cpp create mode 100644 src/gromacs/awh/tests/pmf_target_format0.xvg create mode 100644 src/gromacs/awh/tests/pmf_target_format1.xvg create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_0.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_1.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_2.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_3.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_4.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_5.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_6.xml create mode 100644 src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_7.xml create mode 100644 src/gromacs/mdtypes/awh-history.h create mode 100644 src/gromacs/mdtypes/awh-params.h diff --git a/docs/doxygen/cycle-suppressions.txt b/docs/doxygen/cycle-suppressions.txt index b3dbe47434..7c4fcf1610 100644 --- a/docs/doxygen/cycle-suppressions.txt +++ b/docs/doxygen/cycle-suppressions.txt @@ -12,6 +12,7 @@ mdlib -> essentialdynamics mdlib -> imd mdlib -> ewald mdlib -> pulling +mdlib -> awh simd -> hardware gpu_utils -> hardware topology -> listed-forces diff --git a/docs/doxygen/lib/awh.md b/docs/doxygen/lib/awh.md new file mode 100644 index 0000000000..3234a4671e --- /dev/null +++ b/docs/doxygen/lib/awh.md @@ -0,0 +1,70 @@ +The accelerated weight histogram method (AWH) {#page_awh} +============================================= + +Accelerating sampling with AWH +============================== + +AWH calculates the free energy along an order parameter of the system. +Free energy barriers are overcome by adaptively tuning a bias potential along +the order parameter such that the biased distribution along the parameter +converges toward a chosen target distribution. +The fundamental equation governing the tuning is: log(target) = bias - free energy, where +the bias and free energy are initially unknown. Typically the target distribution is simply +chosen uniform, such that the bias completely flattens the free energy landscape. + + +Design of the AWH module +======================== + +The module implements AWH for the case when the order parameter corresponds to a reaction coordinate, +here referred to as coordinate for short, i.e. a function of the system configuration. +The bias is coupled to the system by a bias potential: either in the form of an harmonic ("umbrella") potential +Monte-Carlo (MC) "jumping" around the current coordinate value, or as a smooth convolution of the umbrellas. + +The AWH module is organizes as follows: +The Awh class is the interface between the outside and inside of the module. +The Awh class contains one or more BiasCoupledToSystem objects. +The BiasCoupledToSystem class takes care of the reaction coordinate input +and force output for the single Bias object it containts. +The Bias class is a container and wrapper for a object BiasState + helpers. +All computation takes place in the BiasState object and its sub-classes. +The Bias class also contains a BiasWriter object that takes care of i/o. + +Use of AWH in mdrun +=================== + +The basic use of Awh in mdrun consists of 2 method calls: +Call the constructor Awh() after the pull module has been initialized. +Call applyBiasForcesAndUpdateBias() at every MD step after the pull +potential calculation function has been called. + +In grompp the pull potential provider should be registered using +registerAwhWithPull() so grompp can check for unregistered potentials. + +The main tasks of AWH are: +- calculate and set the bias force given the current coordinate value. +- after accumulating a number of coordinate samples, update the free energy estimate and the bias. + +AWH currently relies on the pull code for the first task. Pull provides AWH with updated coordinate values +and distributes the bias force that AWH calculates to the atoms making up the coordinate. This +also means that there are some order dependencies where pull functions need to be called before AWH +functions (see below). + +The implementation is quite general. There can be multiple independent AWH biases coupled to the system +simultaneously. This makes sense if the system is made up of several fairly independent parts, +like monomers in a protein. Each bias acts on exactly one, possibly multidimensional, coordinate. +Each coordinate dimension maps to exactly one pull coordinate. Thus, an n-dimensional +biased coordinate is defined by a set of n pull coordinates. Periodicity is taken care of for coordinate +dimensions that require it (dihedral angles). For increased parallelism, there is the option of +having multiple communicating simulations sharing all samples. All simulations would then share a single +bias and free energy estimate. Alternatively, one may partition the sampling domain into smaller +subdomains with some overlap and have multiple independent simulations sample each subdomain. + +Note that internally the AWH module keep tracks of free energies in units +of the thermal energy kT. This is because we mostly deal with free energies +in the form of -log(probability) and using any other unit would be bug prone. +All energy type variables are explicitly documented to be in units of kT. +Also the checkpoint and energy file data is in units of kT. The analysis +tool will by default convert energies to kJ/mol, but there is also +a kT option. + diff --git a/docs/doxygen/user/mainpage.md b/docs/doxygen/user/mainpage.md index 7090ff9b4a..dbe55531af 100644 --- a/docs/doxygen/user/mainpage.md +++ b/docs/doxygen/user/mainpage.md @@ -65,6 +65,9 @@ give an overview of some of the topics that are documented: - \subpage page_simd
Documentation about the new SIMD module that makes it possible to write highly accelerated CPU code that is still portable. + - \subpage page_awh
+ Documentation about the accelerated weight histogram (AWH) method, + which is used for accelerating sampling along reaction coordinates. - \subpage page_mdmodules
Documentation for work-in-progress modularization of parts of mdrun, that should make it easier to implement additional features as add-ons on top of diff --git a/docs/user-guide/mdp-options.rst b/docs/user-guide/mdp-options.rst index b64e9e486b..f708d8eba9 100644 --- a/docs/user-guide/mdp-options.rst +++ b/docs/user-guide/mdp-options.rst @@ -1823,6 +1823,283 @@ applicable pulling coordinate. :mdp:`free-energy` is turned on. The force constant is then (1 - lambda) * :mdp:`pull-coord1-k` + lambda * :mdp:`pull-coord1-kB`. +AWH adaptive biasing +^^^^^^^^^^^^^^^^^^^^ + +.. mdp:: awh + + .. mdp-value:: no + + No biasing. + + .. mdp-value:: yes + + Adaptively bias a reaction coordinate using the AWH method and estimate + the corresponding PMF. The PMF and other AWH data are written to energy + file at an interval set by :mdp:`awh-nstout` and can be extracted with + the ``gmx awh`` tool. The AWH coordinate can be + multidimensional and is defined by mapping each dimension to a pull coordinate index. + This is only allowed if :mdp-value:`pull-coord1-type=external-potential` and + :mdp:`pull-coord1-potential-provider` = ``awh`` for the concerned pull coordinate + indices. + +.. mdp:: awh-potential + + .. mdp-value:: convolved + + The applied biasing potential is the convolution of the bias function and a + set of harmonic umbrella potentials (see :mdp-value:`awh-potential=umbrella` below). This results + in a smooth potential function and force. The resolution of the potential is set + by the force constant of each umbrella, see :mdp:`awh1-dim1-force-constant`. + + .. mdp-value:: umbrella + + The potential bias is applied by controlling the position of an harmonic potential + using Monte-Carlo sampling. The force constant is set with + :mdp:`awh1-dim1-force-constant`. The umbrella location + is sampled using Monte-Carlo every :mdp:`awh-nstsample` steps. + This option can be useful for cases when calculating the convolved force for each step becomes + computationally expensive. + +.. mdp:: awh-share-multisim + + .. mdp-value:: no + + AWH will not share biases across simulations started with + :ref:`gmx mdrun` option ``-multidir``. The biases will be independent. + + .. mdp-value:: yes + + With :ref:`gmx mdrun` and option ``-multidir`` the bias and PMF estimates + for biases with :mdp:`awh1-share-group` >0 will be shared across simulations + with the biases with the same :mdp:`awh1-share-group` value. + The simulations should have the same AWH settings for sharing to make sense. + :ref:`gmx mdrun` will check whether the simulations are technically + compatible for sharing, but the user should check that bias sharing + physically makes sense. + +.. mdp:: awh-seed + + (-1) Random seed for Monte-Carlo sampling the umbrella position, + where -1 indicates to generate a seed. Only used with + :mdp-value:`awh-potential=umbrella`. + +.. mdp:: awh-nstout + + (100000) + Number of steps between printing AWH data to the energy file, should be + a multiple of :mdp:`nstenergy`. + +.. mdp:: awh-nstsample + + (10) + Number of steps between sampling of the coordinate value. This sampling + is the basis for updating the bias and estimating the PMF and other AWH observables. + +.. mdp:: awh-nsamples-update + + (10) + The number of coordinate samples used for each AWH update. + The update interval in steps is :mdp:`awh-nstsample` times this value. + +.. mdp:: awh-nbias + + (1) + The number of biases, each acting on its own coordinate. + The following options should be specified + for each bias although below only the options for bias number 1 is shown. Options for + other bias indices are obtained by replacing '1' by the bias index. + +.. mdp:: awh1-error-init + + (10.0) \[kJ mol-1\] + Estimated initial average error of the PMF for this bias. This value together with the + given diffusion constant(s) :mdp:`awh1-dim1-diffusion` determine the initial biasing rate. + The error is obviously not known *a priori*. Only a rough estimate of :mdp:`awh1-error-init` + is needed however. + As a general guideline, leave :mdp:`awh1-error-init` to its default value when starting a new + simulation. On the other hand, when there is *a priori* knowledge of the PMF (e.g. when + an initial PMF estimate is provided, see the :mdp:`awh1-user-data` option) + then :mdp:`awh1-error-init` should reflect that knowledge. + +.. mdp:: awh1-growth + + .. mdp-value:: exp-linear + + Each bias keeps a reference weight histogram for the coordinate samples. + Its size sets the magnitude of the bias function and free energy estimate updates + (few samples corresponds to large updates and vice versa). + Thus, its growth rate sets the maximum convergence rate. + By default, there is an initial stage in which the histogram grows close to exponentially (but slower than the sampling rate). + In the final stage that follows, the growth rate is linear and equal to the sampling rate (set by :mdp:`awh-nstsample`). + The initial stage is typically necessary for efficient convergence when starting a new simulation where + high free energy barriers have not yet been flattened by the bias. + + .. mdp-value:: linear + + As :mdp-value:`awh1-growth=exp-linear` but skip the initial stage. This may be useful if there is *a priori* + knowledge (see :mdp:`awh1-error-init`) which eliminates the need for an initial stage. This is also + the setting compatible with :mdp-value:`awh1-target=local-boltzmann`. + +.. mdp:: awh1-equilibrate-histogram + + .. mdp-value:: no + + Do not equilibrate histogram. + + .. mdp-value:: yes + + Before entering the initial stage (see :mdp-value:`awh1-growth=exp-linear`), make sure the + histogram of sampled weights is following the target distribution closely enough (specifically, + at least 80% of the target region needs to have a local relative error of less than 20%). This + option would typically only be used when :mdp:`awh1-share-group` > 0 + and the initial configurations poorly represent the target + distribution. + +.. mdp:: awh1-target + + .. mdp-value:: constant + + The bias is tuned towards a constant (uniform) coordinate distribution + in the defined sampling interval (defined by \[:mdp:`awh1-dim1-start`, :mdp:`awh1-dim1-end`\]). + + .. mdp-value:: cutoff + + Similar to :mdp-value:`awh1-target=constant`, but the target + distribution is proportional to 1/(1 + exp(F - :mdp-value:`awh1-target=cutoff`)), + where F is the free energy relative to the estimated global minimum. + This provides a smooth switch of a flat target distribution in + regions with free energy lower than the cut-off to a Boltzmann + distribution in regions with free energy higher than the cut-off. + + .. mdp-value:: boltzmann + + The target distribution is a Boltzmann distribtution with a scaled beta (inverse temperature) + factor given by :mdp:`awh1-target-beta-scaling`. *E.g.*, a value of 0.1 + would give the same coordinate distribution as sampling with a simulation temperature + scaled by 10. + + .. mdp-value:: local-boltzmann + + Same target distribution and use of :mdp:`awh1-target-beta-scaling` + but the convergence towards the target distribution is inherently local *i.e.*, the rate of + change of the bias only depends on the local sampling. This local convergence property is + only compatible with :mdp-value:`awh1-growth=linear`, since for + :mdp-value:`awh1-growth=exp-linear` histograms are globally rescaled in the initial stage. + +.. mdp:: awh1-target-beta-scaling + + [0] \[\] + For :mdp-value:`awh1-target=boltzmann` and :mdp-value:`awh1-target=local-boltzmann` + it is the unitless beta scaling factor taking values in (0,1). + +.. mdp:: awh1-target-cutoff + + [0] \[kJ mol-1\] + For :mdp-value:`awh1-target=cutoff` this is the cutoff, should be > 0. + +.. mdp:: awh1-user-data + + .. mdp-value:: no + + Initialize the PMF and target distribution with default values. + + .. mdp-value:: yes + + Initialize the PMF and target distribution with user provided data. For :mdp:`awh-nbias` = 1, + :ref:`gmx mdrun` will expect a file ``awh-init.xvg`` to be present in the run directory. + For multiple biases, :ref:`gmx mdrun` expects files ``awh-init1.xvg``, ``awh-init2.xvg``, etc. + The file name can be changed with the ``-awh`` option. + The first :mdp:`awh1-ndim` columns of + each input file should contain the coordinate values, such that each row defines a point in + coordinate space. Column :mdp:`awh1-ndim` + 1 should contain the PMF value for each point. + The target distribution column can either follow the PMF (column :mdp:`awh1-ndim` + 2) or + be in the same column as written by ``gmx awh``. + +.. mdp:: awh1-share-group + + .. mdp-value:: 0 + + Do not share the bias. + + .. mdp-value:: positive + + Share the bias and PMF estimates within and/or between simulations. + Within a simulation, the bias will be shared between biases that have the + same :mdp:`awh1-share-group` index (note that the current code does not support this). + With :mdp-value:`awh-share-multisim=yes` and + :ref:`gmx mdrun` option ``-multidir`` the bias will also be shared across simulations. + Sharing may increase convergence initially, although the starting configurations + can be critical, especially when sharing between many biases. + Currently, positive group values should start at 1 and increase + by 1 for each subsequent bias that is shared. + +.. mdp:: awh1-ndim + + (1) \[integer\] + Number of dimensions of the coordinate, each dimension maps to 1 pull coordinate. + The following options should be specified for each such dimension. Below only + the options for dimension number 1 is shown. Options for other dimension indices are + obtained by replacing '1' by the dimension index. + +.. mdp:: awh1-dim1-coord-provider + + .. mdp-value:: pull + + The module providing the reaction coordinate for this dimension. + Currently AWH can only act on pull coordinates. + +.. mdp:: awh1-dim1-coord-index + + (1) + Index of the pull coordinate defining this coordinate dimension. + +.. mdp:: awh1-dim1-force-constant + + (0) \[kJ/mol/nm^2\] or \[kJ/mol/rad^2\] + Force constant for the (convolved) umbrella potential(s) along this + coordinate dimension. + +.. mdp:: awh1-dim1-start + + (0.0) \[nm\]/\[rad\] + Start value of the sampling interval along this dimension. The range of allowed + values depends on the relevant pull geometry (see :mdp:`pull-coord1-geometry`). + For periodic geometries :mdp:`awh1-dim1-start` greater than :mdp:`awh1-dim1-end` + is allowed. The interval will then wrap around from +period/2 to -period/2. + +.. mdp:: awh1-dim1-end + + (0.0) \[nm\]/\[rad\] + End value defining the sampling interval together with :mdp:`awh1-dim1-start`. + +.. mdp:: awh1-dim1-period + + (0.0) \[nm\]/\[rad\] + The period of this reaction coordinate, use 0 when the coordinate is not periodic. + +.. mdp:: awh1-dim1-diffusion + + (1e-5) \[nm^2/ps\]/\[rad^2/ps\] + Estimated diffusion constant for this coordinate dimension determining the initial + biasing rate. This needs only be a rough estimate and should not critically + affect the results unless it is set to something very low, leading to slow convergence, + or very high, forcing the system far from equilibrium. Not setting this value + explicitly generates a warning. + +.. mdp:: awh1-dim1-cover-diameter + + (0.0)) \[nm\]/\[rad\] + Diameter that needs to be sampled by a single simulation around a coordinate value + before the point is considered covered in the initial stage (see :mdp-value:`awh1-growth=exp-linear`). + A value > 0 ensures that for each covering there is a continuous transition of this diameter + across each coordinate value. + This is trivially true for independent simulations but not for for multiple bias-sharing simulations + (:mdp:`awh1-share-group`>0). + For a diameter = 0, covering occurs as soon as the simulations have sampled the whole interval, which + for many sharing simulations does not guarantee transitions across free energy barriers. + On the other hand, when the diameter >= the sampling interval length, covering occurs when a single simulation + has independently sampled the whole interval. Enforced rotation ^^^^^^^^^^^^^^^^^ diff --git a/src/gromacs/CMakeLists.txt b/src/gromacs/CMakeLists.txt index 333405f21e..cf48a183b7 100644 --- a/src/gromacs/CMakeLists.txt +++ b/src/gromacs/CMakeLists.txt @@ -101,6 +101,7 @@ add_subdirectory(fileio) add_subdirectory(swap) add_subdirectory(essentialdynamics) add_subdirectory(pulling) +add_subdirectory(awh) add_subdirectory(simd) add_subdirectory(imd) if (NOT GMX_BUILD_MDRUN_ONLY) diff --git a/src/gromacs/awh/CMakeLists.txt b/src/gromacs/awh/CMakeLists.txt new file mode 100644 index 0000000000..8fc400a9e7 --- /dev/null +++ b/src/gromacs/awh/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# This file is part of the GROMACS molecular simulation package. +# +# Copyright (c) 2014,2015,2016,2017, by the GROMACS development team, led by +# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, +# and including many others, as listed in the AUTHORS file in the +# top-level source directory and at http://www.gromacs.org. +# +# GROMACS is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# GROMACS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with GROMACS; if not, see +# http://www.gnu.org/licenses, or write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# If you want to redistribute modifications to GROMACS, please +# consider that scientific software is very special. Version +# control is crucial - bugs must be traceable. We will be happy to +# consider code for inclusion in the official distribution, but +# derived work must not be called official GROMACS. Details are found +# in the README & COPYING files - if they are missing, get the +# official version at http://www.gromacs.org. +# +# To help us fund GROMACS development, we humbly ask that you cite +# the research papers on the package. Check out http://www.gromacs.org. + +file(GLOB AWH_SOURCES *.cpp) +set(LIBGROMACS_SOURCES ${LIBGROMACS_SOURCES} ${AWH_SOURCES} PARENT_SCOPE) + +if (BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/src/gromacs/awh/awh.cpp b/src/gromacs/awh/awh.cpp new file mode 100644 index 0000000000..02ff69114f --- /dev/null +++ b/src/gromacs/awh/awh.cpp @@ -0,0 +1,344 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the Awh class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "awh.h" + +#include + +#include +#include +#include +#include + +#include + +#include "gromacs/gmxlib/network.h" +#include "gromacs/math/units.h" +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/mdtypes/forceoutput.h" +#include "gromacs/mdtypes/inputrec.h" +#include "gromacs/mdtypes/pull-params.h" +#include "gromacs/mdtypes/state.h" +#include "gromacs/pbcutil/pbc.h" +#include "gromacs/pulling/pull.h" +#include "gromacs/timing/wallcycle.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/pleasecite.h" + +#include "bias.h" +#include "biassharing.h" +#include "pointstate.h" + +namespace gmx +{ + +/*! \internal + * \brief A bias and its coupling to the system. + * + * This struct is used to separate the bias machinery in the Bias class, + * which should be independent from the reaction coordinate, from the + * obtaining of the reaction coordinate values and passing the computed forces. + * Currently the AWH method couples to the system by mapping each + * AWH bias to a pull coordinate. This can easily be generalized here. + */ +struct BiasCoupledToSystem +{ + /*! \brief Constructor, couple a bias to a set of pull coordinates. + * + * \param[in] bias The bias. + * \param[in] pullCoordIndex The pull coordinate indices. + */ + BiasCoupledToSystem(Bias bias, + const std::vector &pullCoordIndex); + + Bias bias; /**< The bias. */ + const std::vector pullCoordIndex; /**< The pull coordinates this bias acts on. */ + + /* Here AWH can be extended to work on other coordinates than pull. */ +}; + +BiasCoupledToSystem::BiasCoupledToSystem(Bias bias, + const std::vector &pullCoordIndex) : + bias(bias), + pullCoordIndex(pullCoordIndex) +{ + /* We already checked for this in grompp, but check again here. */ + GMX_RELEASE_ASSERT(static_cast(bias.ndim()) == pullCoordIndex.size(), "The bias dimensionality should match the number of pull coordinates."); +} + +Awh::Awh(FILE *fplog, + const t_inputrec &inputRecord, + const t_commrec *commRecord, + const AwhParams &awhParams, + const std::string &biasInitFilename, + pull_t *pull_work) : + seed_(awhParams.seed), + commRecord_(commRecord), + pull_(pull_work), + potentialOffset_(0) +{ + /* We already checked for this in grompp, but check again here. */ + GMX_RELEASE_ASSERT(inputRecord.pull != nullptr, "With AWH we should have pull parameters"); + GMX_RELEASE_ASSERT(pull_work != nullptr, "With AWH pull should be initialized before initializing AWH"); + + if (fplog != nullptr) + { + please_cite(fplog, "Lindahl2014"); + } + + if (haveBiasSharingWithinSimulation(awhParams)) + { + /* This has likely been checked by grompp, but throw anyhow. */ + GMX_THROW(InvalidInputError("Biases within a simulation are shared, currently sharing of biases is only supported between simulations")); + } + + int numSharingSimulations = 1; + if (awhParams.shareBiasMultisim && MULTISIM(commRecord_)) + { + numSharingSimulations = commRecord_->ms->nsim; + } + + /* Initialize all the biases */ + const double beta = 1/(BOLTZ*inputRecord.opts.ref_t[0]); + for (int k = 0; k < awhParams.numBias; k++) + { + const AwhBiasParams &awhBiasParams = awhParams.awhBiasParams[k]; + + std::vector pullCoordIndex; + std::vector dimParams; + for (int d = 0; d < awhBiasParams.ndim; d++) + { + const AwhDimParams &awhDimParams = awhBiasParams.dimParams[d]; + GMX_RELEASE_ASSERT(awhDimParams.eCoordProvider == eawhcoordproviderPULL, "Currently only the pull code is supported as coordinate provider"); + const t_pull_coord &pullCoord = inputRecord.pull->coord[awhDimParams.coordIndex]; + double conversionFactor = pull_coordinate_is_angletype(&pullCoord) ? DEG2RAD : 1; + dimParams.push_back(DimParams(conversionFactor, awhDimParams.forceConstant, beta)); + + pullCoordIndex.push_back(awhDimParams.coordIndex); + } + + /* Construct the bias and couple it to the system. */ + Bias::ThisRankWillDoIO thisRankWillDoIO = (MASTER(commRecord_) ? Bias::ThisRankWillDoIO::Yes : Bias::ThisRankWillDoIO::No); + biasCoupledToSystem_.emplace_back(Bias(k, awhParams, awhParams.awhBiasParams[k], dimParams, beta, inputRecord.delta_t, numSharingSimulations, biasInitFilename, thisRankWillDoIO), + pullCoordIndex); + } + + /* Need to register the AWH coordinates to be allowed to apply forces to the pull coordinates. */ + registerAwhWithPull(awhParams, pull_); + + if (numSharingSimulations > 1 && MASTER(commRecord_)) + { + std::vector pointSize; + for (auto const &biasCts : biasCoupledToSystem_) + { + pointSize.push_back(biasCts.bias.state().points().size()); + } + /* Ensure that the shared biased are compatible between simulations */ + biasesAreCompatibleForSharingBetweenSimulations(awhParams, pointSize, commRecord_->ms); + } +} + +Awh::~Awh() = default; + +real Awh::applyBiasForcesAndUpdateBias(int ePBC, + const t_mdatoms &mdatoms, + const matrix box, + gmx::ForceWithVirial *forceWithVirial, + double t, + gmx_int64_t step, + gmx_wallcycle *wallcycle, + FILE *fplog) +{ + GMX_ASSERT(forceWithVirial, "Need a valid ForceWithVirial object"); + + wallcycle_start(wallcycle, ewcAWH); + + t_pbc pbc; + set_pbc(&pbc, ePBC, box); + + /* During the AWH update the potential can instantaneously jump due to either + an bias update or moving the umbrella. The jumps are kept track of and + subtracted from the potential in order to get a useful conserved energy quantity. */ + double awhPotential = potentialOffset_; + + for (auto &biasCts : biasCoupledToSystem_) + { + /* Update the AWH coordinate values with those of the corresponding + * pull coordinates. + */ + awh_dvec coordValue = { 0, 0, 0, 0 }; + for (int d = 0; d < biasCts.bias.ndim(); d++) + { + coordValue[d] = get_pull_coord_value(pull_, biasCts.pullCoordIndex[d], &pbc); + } + + /* Perform an AWH biasing step: this means, at regular intervals, + * sampling observables based on the input pull coordinate value, + * setting the bias force and/or updating the AWH bias state. + */ + awh_dvec biasForce; + double biasPotential; + double biasPotentialJump; + /* Note: In the near future this call will be split in calls + * to supports bias sharing within a single simulation. + */ + biasCts.bias.calcForceAndUpdateBias(coordValue, biasForce, + &biasPotential, &biasPotentialJump, + commRecord_->ms, + t, step, seed_, fplog); + + awhPotential += biasPotential; + + /* Keep track of the total potential shift needed to remove the potential jumps. */ + potentialOffset_ -= biasPotentialJump; + + /* Communicate the bias force to the pull struct. + * The bias potential is returned at the end of this function, + * so that it can be added externally to the correct energy data block. + */ + for (int d = 0; d < biasCts.bias.ndim(); d++) + { + apply_external_pull_coord_force(pull_, biasCts.pullCoordIndex[d], + biasForce[d], &mdatoms, + forceWithVirial); + } + } + + wallcycle_stop(wallcycle, ewcAWH); + + return MASTER(commRecord_) ? static_cast(awhPotential) : 0; +} + +std::shared_ptr Awh::initHistoryFromState() const +{ + if (MASTER(commRecord_)) + { + std::shared_ptr awhHistory(new AwhHistory); + awhHistory->bias.clear(); + awhHistory->bias.resize(biasCoupledToSystem_.size()); + + for (size_t k = 0; k < awhHistory->bias.size(); k++) + { + biasCoupledToSystem_[k].bias.initHistoryFromState(&awhHistory->bias[k]); + } + + return awhHistory; + } + else + { + /* Return an empty pointer */ + return std::shared_ptr(); + } +} + +void Awh::restoreStateFromHistory(const AwhHistory *awhHistory) +{ + /* Restore the history to the current state */ + if (MASTER(commRecord_)) + { + GMX_RELEASE_ASSERT(awhHistory != nullptr, "The master rank should have a valid awhHistory when restoring the state from history."); + + if (awhHistory->bias.size() != biasCoupledToSystem_.size()) + { + GMX_THROW(InvalidInputError("AWH state and history contain different numbers of biases. Likely you provided a checkpoint from a different simulation.")); + } + + potentialOffset_ = awhHistory->potentialOffset; + } + if (PAR(commRecord_)) + { + gmx_bcast(sizeof(potentialOffset_), &potentialOffset_, commRecord_); + } + + for (size_t k = 0; k < biasCoupledToSystem_.size(); k++) + { + biasCoupledToSystem_[k].bias.restoreStateFromHistory(awhHistory ? &awhHistory->bias[k] : nullptr, commRecord_); + } +} + +void Awh::updateHistory(AwhHistory *awhHistory) const +{ + if (!MASTER(commRecord_)) + { + return; + } + + /* This assert will also catch a non-master rank calling this function. */ + GMX_RELEASE_ASSERT(awhHistory->bias.size() == biasCoupledToSystem_.size(), "AWH state and history bias count should match"); + + awhHistory->potentialOffset = potentialOffset_; + + for (size_t k = 0; k < awhHistory->bias.size(); k++) + { + biasCoupledToSystem_[k].bias.updateHistory(&awhHistory->bias[k]); + } +} + +const char * Awh::externalPotentialString() +{ + return "AWH"; +} + +void Awh::registerAwhWithPull(const AwhParams &awhParams, + pull_t *pull_work) +{ + GMX_RELEASE_ASSERT(pull_work, "Need a valid pull object"); + + for (int k = 0; k < awhParams.numBias; k++) + { + const AwhBiasParams &biasParams = awhParams.awhBiasParams[k]; + + for (int d = 0; d < biasParams.ndim; d++) + { + register_external_pull_potential(pull_work, biasParams.dimParams[d].coordIndex, Awh::externalPotentialString()); + } + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/awh.h b/src/gromacs/awh/awh.h new file mode 100644 index 0000000000..f074bbbc0d --- /dev/null +++ b/src/gromacs/awh/awh.h @@ -0,0 +1,234 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \libinternal + * \defgroup module_awh Accelerated weight histogram (AWH) method + * \ingroup group_mdrun + * \brief + * Implements the "accelerated weight histogram" sampling method. + * + * This class provides the interface between the AWH module and + * other modules using it. Currently AWH can only act on COM pull + * reaction coordinates, but this can easily be extended to other + * types of reaction coordinates. + * + * \author Viveca Lindahl + * \author Berk Hess + */ + +/*! \libinternal \file + * + * \brief + * Declares the Awh class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \inlibraryapi + * \ingroup module_awh + */ + +#ifndef GMX_AWH_H +#define GMX_AWH_H + +#include + +#include +#include +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" + +struct gmx_multisim_t; +struct gmx_wallcycle; +struct pull_work_t; +struct pull_t; +class t_state; +struct t_commrec; +struct t_inputrec; +struct t_mdatoms; + +namespace gmx +{ + +struct AwhHistory; +struct AwhParams; +class Bias; +struct BiasCoupledToSystem; +class ForceWithVirial; + +/*! \libinternal + * \brief Coupling of the accelerated weight histogram method (AWH) with the system. + * + * AWH calculates the free energy along order parameters of the system. + * Free energy barriers are overcome by adaptively tuning a bias potential along + * the order parameter such that the biased distribution along the parameter + * converges toward a chosen target distribution. + * + * The Awh class takes care of the coupling between the system and the AWH + * bias(es). The Awh class contains one or more BiasCoupledToSystem objects. + * The BiasCoupledToSystem class takes care of the reaction coordinate input + * and force output for the single Bias object it containts. + * + * \todo Update parameter reading and checkpointing, when general C++ framework is ready. + */ +class Awh +{ + public: + /*! \brief Construct an AWH at the start of a simulation. + * + * AWH will here also register itself with the pull struct as the + * potential provider for the pull coordinates given as AWH coordinates + * in the user input. This allows AWH to later apply the bias force to + * these coordinate in \ref Awh::applyBiasForcesAndUpdateBias. + * + * \param[in,out] fplog General output file, normally md.log, can be nullptr. + * \param[in] inputRecord General input parameters (as set up by grompp). + * \param[in] commRecord Struct for communication, can be nullptr. + * \param[in] awhParams AWH input parameters, consistent with the relevant parts of \p inputRecord (as set up by grompp). + * \param[in] biasInitFilename Name of file to read PMF and target from. + * \param[in,out] pull_work Pointer to a pull struct which AWH will couple to, has to be initialized, is assumed not to change during the lifetime of the Awh object. + */ + Awh(FILE *fplog, + const t_inputrec &inputRecord, + const t_commrec *commRecord, + const AwhParams &awhParams, + const std::string &biasInitFilename, + pull_t *pull_work); + + /*! \brief Destructor. */ + ~Awh(); + + /*! \brief Peform an AWH update, to be called every MD step. + * + * An update has two tasks: apply the bias force and improve + * the bias and the free energy estimate that AWH keeps internally. + * + * For the first task, AWH retrieves the pull coordinate values from the pull struct. + * With these, the bias potential and forces are calculated. + * The bias force together with the atom forces and virial + * are passed on to pull which applies the bias force to the atoms. + * This is done at every step. + * + * Secondly, coordinate values are regularly sampled and kept by AWH. + * Convergence of the bias and free energy estimate is achieved by + * updating the AWH bias state after a certain number of samples has been collected. + * + * \note Requires that pull_potential from pull.h has been called first + * since AWH needs the current coordinate values (the pull code checks + * for this). + * + * \param[in] mdatoms Atom properties. + * \param[in] ePBC Type of periodic boundary conditions. + * \param[in] box Box vectors. + * \param[in,out] forceWithVirial Force and virial buffers, should cover at least the local atoms. + * \param[in] t Time. + * \param[in] step Time step. + * \param[in,out] wallcycle Wallcycle counter, can be nullptr. + * \param[in,out] fplog General output file, normally md.log, can be nullptr. + * \returns the potential energy for the bias. + */ + real applyBiasForcesAndUpdateBias(int ePBC, + const t_mdatoms &mdatoms, + const matrix box, + gmx::ForceWithVirial *forceWithVirial, + double t, + gmx_int64_t step, + gmx_wallcycle *wallcycle, + FILE *fplog); + + /*! \brief + * Update the AWH history in preparation for writing to checkpoint file. + * + * Should be called at least on the master rank at checkpoint steps. + * + * Should be called with a valid \p awhHistory (is checked). + * + * \param[in,out] awhHistory AWH history to set. + */ + void updateHistory(AwhHistory *awhHistory) const; + + /*! \brief + * Allocate and initialize an AWH history with the given AWH state. + * + * This function should be called at the start of a new simulation + * at least on the master rank. + * Note that only constant data will be initialized here. + * History data is set by \ref Awh::updateHistory. + * + * \returns a shared pointer to the AWH history on the rank that does I/O, nullptr otherwise. + */ + std::shared_ptr initHistoryFromState() const; + + /*! \brief Restore the AWH state from the given history. + * + * Should be called on all ranks (for internal MPI broadcast). + * Should pass a point to an AwhHistory on the master rank that + * is compatible with the AWH setup in this simulation. Will throw + * an exception if it is not compatible. + * + * \param[in] awhHistory AWH history to restore from. + */ + void restoreStateFromHistory(const AwhHistory *awhHistory); + + /*! \brief Returns string "AWH" for registering AWH as an external potential provider with the pull module. + */ + static const char *externalPotentialString(); + + /*! \brief Register the AWH biased coordinates with pull. + * + * This function is public because it needs to be called by grompp + * (and is otherwise only called by Awh()). + * Pull requires all external potentials to register themselves + * before the end of pre-processing and before the first MD step. + * If this has not happened, pull with throw an error. + * + * \param[in] awhParams The AWH parameters. + * \param[in,out] pull_work Pull struct which AWH will register the bias into. + */ + static void registerAwhWithPull(const AwhParams &awhParams, + pull_t *pull_work); + + private: + std::vector biasCoupledToSystem_; /**< AWH biases and definitions of their coupling to the system. */ + const gmx_int64_t seed_; /**< Random seed for MC jumping with umbrella type bias potential. */ + const t_commrec *commRecord_; /**< Pointer to the communication record. */ + pull_t *pull_; /**< Pointer to the pull working data. */ + double potentialOffset_; /**< The offset of the bias potential which changes due to bias updates. */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_H */ diff --git a/src/gromacs/awh/bias.cpp b/src/gromacs/awh/bias.cpp new file mode 100644 index 0000000000..8a314fdee6 --- /dev/null +++ b/src/gromacs/awh/bias.cpp @@ -0,0 +1,255 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the Bias class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "bias.h" + +#include + +#include +#include +#include +#include + +#include + +#include "gromacs/fileio/gmxfio.h" +#include "gromacs/gmxlib/network.h" +#include "gromacs/math/utilities.h" +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/stringutil.h" + +#include "pointstate.h" + +namespace gmx +{ + +void Bias::warnForHistogramAnomalies(double t, gmx_int64_t step, FILE *fplog) +{ + const int maxNumWarningsInCheck = 1; /* The maximum number of warnings to print per check */ + const int maxNumWarningsInRun = 10; /* The maximum number of warnings to print in a run */ + + if (fplog == nullptr || numWarningsIssued_ >= maxNumWarningsInRun || state_.inInitialStage() || + !params_.isCheckStep(state_.points().size(), step)) + { + return; + } + + numWarningsIssued_ += + state_.warnForHistogramAnomalies(grid_, biasIndex(), t, fplog, + maxNumWarningsInCheck); + + if (numWarningsIssued_ >= maxNumWarningsInRun) + { + fprintf(fplog, "\nawh%d: suppressing future AWH warnings.\n", biasIndex() + 1); + } +} + +void Bias::doSkippedUpdatesForAllPoints() +{ + state_.doSkippedUpdatesForAllPoints(params_); +} + +void Bias::calcForceAndUpdateBias(const awh_dvec coordValue, + awh_dvec biasForce, + double *awhPotential, + double *potentialJump, + const gmx_multisim_t *ms, + double t, + gmx_int64_t step, + gmx_int64_t seed, + FILE *fplog) +{ + if (step < 0) + { + GMX_THROW(InvalidInputError("The step number is negative which is not supported by the AWH code.")); + } + + state_.setCoordValue(grid_, coordValue); + + std::vector *probWeightNeighbor = &tempWorkSpace_; + + /* If the convolved force is needed or this is a sampling step, + * the bias in the current neighborhood needs to be up-to-date + * and the probablity weights need to be calculated. + */ + const bool sampleCoord = params_.isSampleCoordStep(step); + const bool moveUmbrella = (sampleCoord || step == 0); + double convolvedBias = 0; + if (params_.convolveForce || moveUmbrella || sampleCoord) + { + if (params_.skipUpdates()) + { + state_.doSkippedUpdatesInNeighborhood(params_, grid_); + } + + convolvedBias = state_.updateProbabilityWeightsAndConvolvedBias(dimParams_, grid_, probWeightNeighbor); + + if (sampleCoord) + { + state_.sampleCoordAndPmf(grid_, *probWeightNeighbor, convolvedBias); + } + } + + const CoordState &coordState = state_.coordState(); + + /* Set the bias force and get the potential contribution from this bias. + * The potential jump occurs at different times depending on how + * the force is applied (and how the potential is normalized). + * For the convolved force it happens when the bias is updated, + * for the umbrella when the umbrella is moved. + */ + *potentialJump = 0; + double potential; + if (params_.convolveForce) + { + state_.calcConvolvedForce(dimParams_, grid_, *probWeightNeighbor, + biasForce); + + potential = -convolvedBias*params_.invBeta; + } + else + { + /* Umbrella force */ + GMX_RELEASE_ASSERT(state_.points()[coordState.umbrellaGridpoint()].inTargetRegion(), + "AWH bias grid point for the umbrella reference value is outside of the target region."); + potential = + state_.calcUmbrellaForceAndPotential(dimParams_, grid_, coordState.umbrellaGridpoint(), biasForce); + + /* Moving the umbrella results in a force correction and + * a new potential. The umbrella center is sampled as often as + * the coordinate so we know the probability weights needed + * for moving the umbrella are up-to-date. + */ + if (moveUmbrella) + { + double newPotential = state_.moveUmbrella(dimParams_, grid_, *probWeightNeighbor, biasForce, step, seed, params_.biasIndex); + *potentialJump = newPotential - potential; + } + } + + /* Update the free energy estimates and bias and other history dependent method parameters */ + if (params_.isUpdateFreeEnergyStep(step)) + { + state_.updateFreeEnergyAndAddSamplesToHistogram(dimParams_, grid_, + params_, + ms, t, step, fplog, + &updateList_); + + if (params_.convolveForce) + { + /* The update results in a potential jump, so we need the new convolved potential. */ + double newPotential = -calcConvolvedBias(coordState.coordValue())*params_.invBeta; + *potentialJump = newPotential - potential; + } + } + + /* Return the potential. */ + *awhPotential = potential; + + /* Check the sampled histograms and potentially warn user if something is suspicious */ + warnForHistogramAnomalies(t, step, fplog); +} + +void Bias::restoreStateFromHistory(const AwhBiasHistory *biasHistory, + const t_commrec *cr) +{ + GMX_RELEASE_ASSERT(thisRankDoesIO_ == MASTER(cr), "The master rank should do I/O, the other ranks should not"); + + if (MASTER(cr)) + { + GMX_RELEASE_ASSERT(biasHistory != nullptr, "On the master rank we need a valid history object to restore from"); + state_.restoreFromHistory(*biasHistory, grid_); + } + + if (PAR(cr)) + { + state_.broadcast(cr); + } +} + +void Bias::initHistoryFromState(AwhBiasHistory *biasHistory) const +{ + GMX_RELEASE_ASSERT(biasHistory != nullptr, "Need a valid biasHistory"); + + state_.initHistoryFromState(biasHistory); +} + +void Bias::updateHistory(AwhBiasHistory *biasHistory) const +{ + GMX_RELEASE_ASSERT(biasHistory != nullptr, "Need a valid biasHistory"); + + state_.updateHistory(biasHistory, grid_); +} + +Bias::Bias(int biasIndexInCollection, + const AwhParams &awhParams, + const AwhBiasParams &awhBiasParams, + const std::vector &dimParamsInit, + double beta, + double mdTimeStep, + int numSharingSimulations, + const std::string &biasInitFilename, + ThisRankWillDoIO thisRankWillDoIO, + BiasParams::DisableUpdateSkips disableUpdateSkips) : + dimParams_(dimParamsInit), + grid_(dimParamsInit, awhBiasParams.dimParams), + params_(awhParams, awhBiasParams, dimParams_, beta, mdTimeStep, disableUpdateSkips, numSharingSimulations, grid_.axis(), biasIndexInCollection), + state_(awhBiasParams, params_.initialHistogramSize, dimParams_, grid_), + thisRankDoesIO_(thisRankWillDoIO == ThisRankWillDoIO::Yes), + tempWorkSpace_(), + numWarningsIssued_(0) +{ + /* For a global update updateList covers all points, so reserve that */ + updateList_.reserve(grid_.numPoints()); + + state_.initGridPointState(awhBiasParams, dimParams_, grid_, params_, biasInitFilename, awhParams.numBias); +} + +} // namespace gmx diff --git a/src/gromacs/awh/bias.h b/src/gromacs/awh/bias.h new file mode 100644 index 0000000000..4d5625b0b0 --- /dev/null +++ b/src/gromacs/awh/bias.h @@ -0,0 +1,313 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the Bias class. + * + * This class is essentially a wrapper around the BiasState class. + * In addition to BiasState, it holds all data that BiasState needs + * to update the bias. Interaction of the outside world, such as updating + * BiasState or extracting bias data all happen through Bias. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_BIAS_H +#define GMX_AWH_BIAS_H + +#include +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" +#include "gromacs/utility/gmxassert.h" + +#include "biasparams.h" +#include "biasstate.h" +#include "dimparams.h" +#include "grid.h" + +struct gmx_multisim_t; +struct t_commrec; + +namespace gmx +{ + +struct AwhBiasHistory; +struct AwhBiasParams; +struct AwhHistory; +struct AwhParams; +struct AwhPointStateHistory; +class Grid; +class GridAxis; +class PointState; + +/*! \internal + * \brief A bias acting on a multidimensional coordinate. + * + * At each step AWH should provide its biases with updated + * values of their coordinates. Each bias provides AWH with an updated + * bias forces and the corresponding potential. + * + * See the user manual for details on the algorithm and equations. + * + * The bias is responsible for keeping and updating a free energy estimate + * along the coordinate. The bias potential is basically a function of the + * free energy estimate and so also changes by the update. + * The free energy update is based on information from coordinate samples + * collected at a constant bias potential, between updates. + * + * The bias keeps a grid with coordinate points that organizes spatial + * information about the coordinate. The grid has the the same geometry + * as the coordinate, i.e. they have the same dimensionality and periodicity + * (if any). The number of points in the grid sets the resolution of + * the collected data and its extent defines the sampling region of interest. + * + * Each coordinate point has further statistical properties and function values + * which a grid point does not know about. E.g., for the bias each coordinate point + * is associated with values of the bias, free energy and target distribution, + * accumulated sampling weight, etc. For this the bias attaches to each grid + * point a state. The grid + vector of point states are the bias coordinate points. + * + * The bias has a fairly complex global state keeping track of where + * the system (coordinate) currently is (CoordState), where it has + * sampled since the last update (BiasState) and controlling the free energy + * convergence rate (HistogramSize). + * + * Partly, the complexity comes from the bias having two convergence stages: + * an initial stage which in an heuristic, non-deterministic way restricts + * the early convergence rate for sake of robustness; and a final stage + * where the convergence rate is constant. The length of the initial stage + * depends on the sampling and is unknown beforehand. + * + * Another complexity comes from the fact that coordinate points, + * for sake of efficiency in the case of many grid points, are typically + * only accessed in recently sampled regions even though the free energy + * update is inherently global and affects all points. + * The bias allows points thay are non-local at the time the update + * was issued to postpone ("skip", as it is called in the code) the update. + * A non-local point is defined as a point which has not been sampled since + * the last update. Local points are points that have been sampled since + * the last update. The (current) set of local points are kept track of by + * the bias state and reset after every update. An update is called local + * if it only updates local points. Non-local points will temporarily "skip" + * the update until next time they are local (or when a global update + * is issued). For this to work, the bias keeps a global "clock" + * (in HistogramSize) of the number of issued updates. Each PointState + * also has its own local "clock" with the counting the number of updates + * it has pulled through. When a point updates its state it asserts + * that its local clock is synchronized with the global clock. + */ +class Bias +{ + public: + //! Enum for requesting Bias set up with(out) I/O on this rank. + enum class ThisRankWillDoIO + { + No, //!< This rank will not do I/O. + Yes //!< This rank will do I/O. + }; + + /*! \brief + * Constructor. + * + * \param[in] biasIndexInCollection Index of the bias in collection. + * \param[in] awhParams AWH parameters. + * \param[in] awhBiasParams Bias parameters. + * \param[in] dimParams Bias dimension parameters. + * \param[in] beta 1/(k_B T). + * \param[in] mdTimeStep The MD time step. + * \param[in] numSharingSimulations The number of simulations to share the bias across. + * \param[in] biasInitFilename Name of file to read PMF and target from. + * \param[in] thisRankWillDoIO Tells whether this MPI rank will do I/O (checkpointing, AWH output), normally (only) the master rank does I/O. + * \param[in] disableUpdateSkips If to disable update skips, useful for testing. + */ + Bias(int biasIndexInCollection, + const AwhParams &awhParams, + const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + double beta, + double mdTimeStep, + int numSharingSimulations, + const std::string &biasInitFilename, + ThisRankWillDoIO thisRankWillDoIO, + BiasParams::DisableUpdateSkips disableUpdateSkips = BiasParams::DisableUpdateSkips::no); + + /*! \brief + * Evolves the bias at every step. + * + * At each step the bias step needs to: + * - set the bias force and potential; + * - update the free energy and bias if needed; + * - reweight samples to extract the PMF. + * + * \param[in] coordValue The current coordinate value(s). + * \param[out] biasForce The bias force. + * \param[out] awhPotential Bias potential. + * \param[out] potentialJump Change in bias potential for this bias. + * \param[in] ms Struct for multi-simulation communication. + * \param[in] t Time. + * \param[in] step Time step. + * \param[in] seed Random seed. + * \param[in,out] fplog Log file. + */ + void calcForceAndUpdateBias(const awh_dvec coordValue, + awh_dvec biasForce, + double *awhPotential, + double *potentialJump, + const gmx_multisim_t *ms, + double t, + gmx_int64_t step, + gmx_int64_t seed, + FILE *fplog); + + /*! \brief + * Calculates the convolved bias for a given coordinate value. + * + * The convolved bias is the effective bias acting on the coordinate. + * Since the bias here has arbitrary normalization, this only makes + * sense as a relative, to other coordinate values, measure of the bias. + * + * \param[in] coordValue The coordinate value. + * \returns the convolved bias >= -GMX_DOUBLE_MAX. + */ + double calcConvolvedBias(const awh_dvec &coordValue) const + { + return state_.calcConvolvedBias(dimParams_, grid_, coordValue); + } + + /*! \brief + * Restore the bias state from history on the master rank and broadcast it. + * + * \param[in] biasHistory Bias history struct, only allowed to be nullptr on non-master ranks. + * \param[in] cr The communication record. + */ + void restoreStateFromHistory(const AwhBiasHistory *biasHistory, + const t_commrec *cr); + + /*! \brief + * Allocate and initialize a bias history with the given bias state. + * + * This function will be called at the start of a new simulation. + * Note that only constant data will be initialized here. + * History data is set by \ref updateHistory. + * + * \param[in,out] biasHistory AWH history to initialize. + */ + void initHistoryFromState(AwhBiasHistory *biasHistory) const; + + /*! \brief + * Update the bias history with the current state. + * + * \param[out] biasHistory Bias history struct. + */ + void updateHistory(AwhBiasHistory *biasHistory) const; + + /*! \brief + * Do all previously skipped updates. + * Public for use by tests. + */ + void doSkippedUpdatesForAllPoints(); + + //! Returns the dimensionality of the bias. + inline int ndim() const + { + return dimParams_.size(); + } + + /*! \brief Returns the dimension parameters. + */ + inline const std::vector &dimParams() const + { + return dimParams_; + } + + //! Returns the bias parameters + inline const BiasParams ¶ms() const + { + return params_; + } + + //! Returns the global state of the bias. + inline const BiasState &state() const + { + return state_; + } + + //! Returns the index of the bias. + inline int biasIndex() const + { + return params_.biasIndex; + } + + private: + /*! \brief + * Performs statistical checks on the collected histograms and warns if issues are detected. + * + * \param[in] t Time. + * \param[in] step Time step. + * \param[in,out] fplog Output file for warnings. + */ + void warnForHistogramAnomalies(double t, + gmx_int64_t step, + FILE *fplog); + + /* Data members. */ + private: + const std::vector dimParams_; /**< Parameters for each dimension. */ + const Grid grid_; /**< The multidimensional grid organizing the coordinate point locations. */ + + const BiasParams params_; /**< Constant parameters for the method. */ + + BiasState state_; /**< The state, both global and of the grid points */ + std::vector updateList_; /**< List of points for update for temporary use (could be made another tempWorkSpace) */ + + const bool thisRankDoesIO_; /**< Tells whether this MPI rank will do I/O (checkpointing, AWH output) */ + + /* Temporary working vector used during the update. + * This only here to avoid allocation at every MD step. + */ + std::vector tempWorkSpace_; /**< Working vector of doubles. */ + + int numWarningsIssued_; /**< The number of warning issued in the current run. */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_BIAS_H */ diff --git a/src/gromacs/awh/biasparams.cpp b/src/gromacs/awh/biasparams.cpp new file mode 100644 index 0000000000..13347147b7 --- /dev/null +++ b/src/gromacs/awh/biasparams.cpp @@ -0,0 +1,310 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the initialization of the BiasParams class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "biasparams.h" + +#include + +#include + +#include "gromacs/math/functions.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" + +#include "grid.h" + +namespace gmx +{ + +bool BiasParams::isCheckStep(std::size_t numPointsInHistogram, + gmx_int64_t step) const +{ + int numStepsUpdateFreeEnergy = numSamplesUpdateFreeEnergy_*numStepsSampleCoord_; + int numStepsCheck = (1 + numPointsInHistogram/numSamplesUpdateFreeEnergy_)*numStepsUpdateFreeEnergy; + + if (step > 0 && step % numStepsCheck == 0) + { + GMX_ASSERT(isUpdateFreeEnergyStep(step), "We should only check at free-energy update steps"); + + return true; + } + else + { + return false; + } +} + +namespace +{ + +/*! \brief Determines the interval for updating the target distribution. + * + * The interval value is based on the target distrbution type + * (this could be made a user-option but there is most likely + * no big need for tweaking this for most users). + * + * \param[in] awhParams AWH parameters. + * \param[in] awhBiasParams Bias parameters. + * \returns the target update interval in steps. + */ +gmx_int64_t calcTargetUpdateInterval(const AwhParams &awhParams, + const AwhBiasParams &awhBiasParams) +{ + gmx_int64_t numStepsUpdateTarget = 0; + /* Set the target update frequency based on the target distrbution type + * (this could be made a user-option but there is most likely no big need + * for tweaking this for most users). + */ + switch (awhBiasParams.eTarget) + { + case eawhtargetCONSTANT: + numStepsUpdateTarget = 0; + break; + case eawhtargetCUTOFF: + case eawhtargetBOLTZMANN: + /* Updating the target generally requires updating the whole grid so to keep the cost down + we generally update the target less often than the free energy (unless the free energy + update step is set to > 100 samples). */ + numStepsUpdateTarget = std::max(100 % awhParams.numSamplesUpdateFreeEnergy, + awhParams.numSamplesUpdateFreeEnergy)*awhParams.nstSampleCoord; + break; + case eawhtargetLOCALBOLTZMANN: + /* The target distribution is set equal to the reference histogram which is updated every free energy update. + So the target has to be updated at the same time. This leads to a global update each time because it is + assumed that a target distribution update can take any form. This is a bit unfortunate for a "local" + target distribution. One could avoid the global update by making a local target update function (and + postponing target updates for non-local points as for the free energy update). We avoid such additions + for now and accept that this target type always does global updates. */ + numStepsUpdateTarget = awhParams.numSamplesUpdateFreeEnergy*awhParams.nstSampleCoord; + break; + default: + GMX_RELEASE_ASSERT(false, "Unknown AWH target type"); + break; + } + + return numStepsUpdateTarget; +} + +/*! \brief + * Returns an approximation of the geometry factor used for initializing the AWH update size. + * + * The geometry factor is defined as the following sum of Gaussians: + * sum_{k!=0} exp(-0.5*(k*pi*x)^2)/(pi*k)^2, + * where k is a xArray.size()-dimensional integer vector with k_i in {0,1,..}. + * + * \param[in] xArray Array to evaluate. + * \returns the geometry factor. + */ +double gaussianGeometryFactor(gmx::ArrayRef xArray) +{ + /* For convenience we give the geometry factor function a name: zeta(x) */ + constexpr size_t tableSize = 5; + std::array xTabulated = + { {1e-5, 1e-4, 1e-3, 1e-2, 1e-1} }; + std::array zetaTable1d = + { { 0.166536811948, 0.16653116886, 0.166250075882, 0.162701098306, 0.129272430287 } }; + std::array zetaTable2d = + { { 2.31985974274, 1.86307292523, 1.38159772648, 0.897554759158, 0.405578211115 } }; + + gmx::ArrayRef zetaTable; + + if (xArray.size() == 1) + { + zetaTable = zetaTable1d; + } + else if (xArray.size() == 2) + { + zetaTable = zetaTable2d; + } + else + { + /* TODO... but this is anyway a rough estimate and > 2 dimensions is not so popular. */ + zetaTable = zetaTable2d; + } + + /* TODO. Really zeta is a function of an ndim-dimensional vector x and we shoudl have a ndim-dimensional lookup-table. + Here we take the geometric average of the components of x which is ok if the x-components are not very different. */ + double xScalar = 1; + for (const double &x : xArray) + { + xScalar *= x; + } + + GMX_ASSERT(xArray.size() > 0, "We should have a non-empty input array"); + xScalar = std::pow(xScalar, 1.0/xArray.size()); + + /* Look up zeta(x) */ + size_t xIndex = 0; + while ((xIndex < xTabulated.size()) && (xScalar > xTabulated[xIndex])) + { + xIndex++; + } + + double zEstimate; + if (xIndex == xTabulated.size()) + { + /* Take last value */ + zEstimate = zetaTable[xTabulated.size() - 1]; + } + else if (xIndex == 0) + { + zEstimate = zetaTable[xIndex]; + } + else + { + /* Interpolate */ + double x0 = xTabulated[xIndex - 1]; + double x1 = xTabulated[xIndex]; + double w = (xScalar - x0)/(x1 - x0); + zEstimate = w*zetaTable[xIndex - 1] + (1 - w)*zetaTable[xIndex]; + } + + return zEstimate; +} + +/*! \brief + * Estimate a reasonable initial reference weight histogram size. + * + * \param[in] dimParams Parameters for the dimensions of the coordinate. + * \param[in] awhBiasParams Bias parameters. + * \param[in] gridAxis The Grid axes. + * \param[in] beta 1/(k_B T). + * \param[in] samplingTimestep Sampling frequency of probability weights. + * \returns estimate of initial histogram size. + */ +double getInitialHistogramSizeEstimate(const std::vector &dimParams, + const AwhBiasParams &awhBiasParams, + const std::vector &gridAxis, + double beta, + double samplingTimestep) +{ + /* Get diffusion factor */ + double crossingTime = 0.; + std::vector x; + for (size_t d = 0; d < gridAxis.size(); d++) + { + double axisLength = gridAxis[d].length(); + if (axisLength > 0) + { + crossingTime += awhBiasParams.dimParams[d].diffusion/(axisLength*axisLength); + /* The sigma of the Gaussian distribution in the umbrella */ + double sigma = 1./std::sqrt(dimParams[d].betak); + x.push_back(sigma/axisLength); + } + } + GMX_RELEASE_ASSERT(crossingTime > 0, "We need at least one dimension with non-zero length"); + double errorInitialInKT = beta*awhBiasParams.errorInitial; + double histogramSize = gaussianGeometryFactor(x)/(crossingTime*gmx::square(errorInitialInKT)*samplingTimestep); + + return histogramSize; +} + +/*! \brief Returns the number of simulations sharing bias updates. + * + * \param[in] awhBiasParams Bias parameters. + * \param[in] numSharingSimulations The number of simulations to share the bias across. + * \returns the number of shared updates. + */ +int getNumSharedUpdate(const AwhBiasParams &awhBiasParams, + int numSharingSimulations) +{ + GMX_RELEASE_ASSERT(numSharingSimulations >= 1, "We should ''share'' at least with ourselves"); + + int numShared = 1; + + if (awhBiasParams.shareGroup > 0) + { + /* We do not yet support sharing within a simulation */ + int numSharedWithinThisSimulation = 1; + numShared = numSharingSimulations*numSharedWithinThisSimulation; + } + + return numShared; +} + +} // namespace + +BiasParams::BiasParams(const AwhParams &awhParams, + const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + double beta, + double mdTimeStep, + DisableUpdateSkips disableUpdateSkips, + int numSharingSimulations, + const std::vector &gridAxis, + int biasIndex) : + invBeta(beta > 0 ? 1/beta : 0), + numStepsSampleCoord_(awhParams.nstSampleCoord), + numSamplesUpdateFreeEnergy_(awhParams.numSamplesUpdateFreeEnergy), + numStepsUpdateTarget_(calcTargetUpdateInterval(awhParams, awhBiasParams)), + eTarget(awhBiasParams.eTarget), + freeEnergyCutoffInKT(beta*awhBiasParams.targetCutoff), + temperatureScaleFactor(awhBiasParams.targetBetaScaling), + idealWeighthistUpdate(eTarget != eawhtargetLOCALBOLTZMANN), + numSharedUpdate(getNumSharedUpdate(awhBiasParams, numSharingSimulations)), + updateWeight(numSamplesUpdateFreeEnergy_*numSharedUpdate), + localWeightScaling(eTarget == eawhtargetLOCALBOLTZMANN ? temperatureScaleFactor : 1), + initialHistogramSize(getInitialHistogramSizeEstimate(dimParams, awhBiasParams, gridAxis, beta, numStepsSampleCoord_*mdTimeStep)), + convolveForce(awhParams.ePotential == eawhpotentialCONVOLVED), + biasIndex(biasIndex), + disableUpdateSkips_(disableUpdateSkips == DisableUpdateSkips::yes) +{ + if (beta <= 0) + { + GMX_THROW(InvalidInputError("To use AWH, the beta=1/(k_B T) should be > 0")); + } + + for (int d = 0; d < awhBiasParams.ndim; d++) + { + double coverRadiusInNm = 0.5*awhBiasParams.dimParams[d].coverDiameter; + double spacing = gridAxis[d].spacing(); + coverRadius_[d] = spacing > 0 ? static_cast(std::round(coverRadiusInNm/spacing)) : 0; + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/biasparams.h b/src/gromacs/awh/biasparams.h new file mode 100644 index 0000000000..ff2938f36d --- /dev/null +++ b/src/gromacs/awh/biasparams.h @@ -0,0 +1,217 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the BiasParams class. + * + * This class holds the parameters for the bias. Most are direct copies + * of the input that the user provided. Some are a combination of user + * input and properties of the simulated system. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_BIASPARAMS_H +#define GMX_AWH_BIASPARAMS_H + +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" + +#include "dimparams.h" + +namespace gmx +{ + +struct AwhBiasParams; +struct AwhParams; +struct DimParams; +class GridAxis; + +/*! \internal \brief Constant parameters for the bias. + */ +class BiasParams +{ + public: + /*! \brief Switch to turn off update skips, useful for testing. + */ + enum class DisableUpdateSkips + { + no, /**< Allow update skips (when supported by the method) */ + yes /**< Disable update skips */ + }; + + /*! \brief + * Check if the parameters permit skipping updates. + * + * Generally, we can skip updates of points that are non-local + * at the time of the update if we for later times, when the points + * with skipped updates have become local, know exactly how to apply + * the previous updates. The free energy updates only depend + * on local sampling, but the histogram rescaling factors + * generally depend on the histogram size (all samples). + * If the histogram size is kept constant or the scaling factors + * are trivial, this is not a problem. However, if the histogram growth + * is scaled down by some factor the size at the time of the update + * needs to be known. It would be fairly simple to, for a deterministically + * growing histogram, backtrack and calculate this value, but currently + * we just disallow this case. This is not a restriction because it + * only affects the local Boltzmann target type for which every update + * is currently anyway global because the target is always updated globally. + * + * \returns true when we can skip updates. + */ + inline bool skipUpdates() const + { + return (!disableUpdateSkips_ && localWeightScaling == 1); + } + + /*! \brief + * Returns the the radius that needs to be sampled around a point before it is considered covered. + */ + inline const awh_ivec &coverRadius() const + { + return coverRadius_; + } + + /*! \brief + * Returns whether we should sample the coordinate. + * + * \param[in] step The MD step number. + */ + inline bool isSampleCoordStep(gmx_int64_t step) const + { + return (step > 0 && step % numStepsSampleCoord_ == 0); + } + + /*! \brief + * Returns whether we should update the free energy. + * + * \param[in] step The MD step number. + */ + inline bool isUpdateFreeEnergyStep(gmx_int64_t step) const + { + int stepIntervalUpdateFreeEnergy = numSamplesUpdateFreeEnergy_*numStepsSampleCoord_; + return (step > 0 && step % stepIntervalUpdateFreeEnergy == 0); + } + + /*! \brief + * Returns whether we should update the target distribution. + * + * \param[in] step The MD step number. + */ + inline bool isUpdateTargetStep(gmx_int64_t step) const + { + return step % numStepsUpdateTarget_ == 0; + } + + /*! \brief + * Returns if to do checks, only returns true at free-energy update steps. + * + * To avoid overhead due to expensive checks, we only do checks when we + * have taken at least as many samples as we have points. + * + * \param[in] numPointsInHistogram The total number of points in the bias histogram. + * \param[in] step Time step. + * \returns true at steps where checks should be performed. + */ + bool isCheckStep(std::size_t numPointsInHistogram, + gmx_int64_t step) const; + + /*! \brief Constructor. + * + * The local Boltzmann target distibution is defined by + * 1) Adding the sampled weights instead of the target weights to the reference weight histogram. + * 2) Scaling the weights of these samples by the beta scaling factor. + * 3) Setting the target distribution equal the reference weight histogram. + * This requires the following special update settings: + * localWeightScaling = targetParam + * idealWeighthistUpdate = false + * Note: these variables could in principle be set to something else also for other target distribution types. + * However, localWeightScaling < 1 is in general expected to give lower efficiency and, except for local Boltzmann, + * idealWeightHistUpdate = false gives (in my experience) unstable, non-converging results. + * + * \param[in] awhParams AWH parameters. + * \param[in] awhBiasParams Bias parameters. + * \param[in] dimParams Bias dimension parameters. + * \param[in] beta 1/(k_B T) in units of 1/(kJ/mol), should be > 0. + * \param[in] mdTimeStep The MD time step. + * \param[in] numSharingSimulations The number of simulations to share the bias across. + * \param[in] gridAxis The grid axes. + * \param[in] disableUpdateSkips If to disable update skips, useful for testing. + * \param[in] biasIndex Index of the bias. + */ + BiasParams(const AwhParams &awhParams, + const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + double beta, + double mdTimeStep, + DisableUpdateSkips disableUpdateSkips, + int numSharingSimulations, + const std::vector &gridAxis, + int biasIndex); + + /* Data members */ + const double invBeta; /**< 1/beta = kT in kJ/mol */ + private: + const gmx_int64_t numStepsSampleCoord_; /**< Number of steps per coordinate value sample. */ + const int numSamplesUpdateFreeEnergy_; /**< Number of samples per free energy update. */ + const gmx_int64_t numStepsUpdateTarget_; /**< Number of steps per updating the target distribution. */ + public: + const int eTarget; /**< Type of target distribution. */ + const double freeEnergyCutoffInKT; /**< Free energy cut-off in kT for cut-off target distribution. */ + const double temperatureScaleFactor; /**< Temperature scaling factor for temperature scaled targed distributions. */ + const bool idealWeighthistUpdate; /**< Update reference weighthistogram using the target distribution? Otherwise use the realized distribution. */ + const int numSharedUpdate; /**< The number of (multi-)simulations sharing the bias update */ + const double updateWeight; /**< The probability weight accumulated for each update. */ + const double localWeightScaling; /**< Scaling factor applied to a sample before adding it to the reference weight histogram (= 1, usually). */ + const double initialHistogramSize; /**< Initial reference weight histogram size. */ + private: + awh_ivec coverRadius_; /**< The radius (in points) that needs to be sampled around a point before it is considered covered. */ + public: + const bool convolveForce; /**< True if we convolve the force, false means use MC between umbrellas. */ + const int biasIndex; /**< Index of the bias, used as a second random seed and for priting. */ + private: + const bool disableUpdateSkips_; /**< If true, we disallow update skips, even when the method supports it. */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_BIASPARAMS_H */ diff --git a/src/gromacs/awh/biassharing.cpp b/src/gromacs/awh/biassharing.cpp new file mode 100644 index 0000000000..3bd98b82b0 --- /dev/null +++ b/src/gromacs/awh/biassharing.cpp @@ -0,0 +1,152 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements bias sharing checking functionality. + * + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "biassharing.h" + +#include + +#include "gromacs/gmxlib/network.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/stringutil.h" + +namespace gmx +{ + +bool haveBiasSharingWithinSimulation(const AwhParams &awhParams) +{ + bool haveSharing = false; + + for (int k = 0; k < awhParams.numBias; k++) + { + int shareGroup = awhParams.awhBiasParams[k].shareGroup; + if (shareGroup > 0) + { + for (int i = k + 1; i < awhParams.numBias; i++) + { + if (awhParams.awhBiasParams[i].shareGroup == shareGroup) + { + haveSharing = true; + } + } + } + } + + return haveSharing; +} + +void biasesAreCompatibleForSharingBetweenSimulations(const AwhParams &awhParams, + const std::vector &pointSize, + const gmx_multisim_t *multiSimComm) +{ + const int numSim = multiSimComm->nsim; + + /* We currently enforce subsequent shared biases to have consecutive + * share-group values starting at 1. This means we can reduce shared + * biases in order over the ranks and it does not restrict possibilities. + */ + int numShare = 0; + for (int b = 0; b < awhParams.numBias; b++) + { + int group = awhParams.awhBiasParams[b].shareGroup; + if (group > 0) + { + numShare++; + if (group != numShare) + { + GMX_THROW(InvalidInputError("AWH biases that are shared should use consequetive share-group values starting at 1")); + } + } + } + std::vector numShareAll(numSim); + numShareAll[multiSimComm->sim] = numShare; + gmx_sumi_sim(numShareAll.size(), numShareAll.data(), multiSimComm); + for (int sim = 1; sim < numSim; sim++) + { + if (numShareAll[sim] != numShareAll[0]) + { + GMX_THROW(InvalidInputError("Different simulations attempt to share different number of biases")); + } + } + + std::vector intervals(numSim*2); + intervals[numSim*0 + multiSimComm->sim] = awhParams.nstSampleCoord; + intervals[numSim*1 + multiSimComm->sim] = awhParams.numSamplesUpdateFreeEnergy; + gmx_sumi_sim(intervals.size(), intervals.data(), multiSimComm); + for (int sim = 1; sim < numSim; sim++) + { + if (intervals[sim] != intervals[0]) + { + GMX_THROW(InvalidInputError("All simulations should have the same AWH sample interval")); + } + if (intervals[numSim + sim] != intervals[numSim]) + { + GMX_THROW(InvalidInputError("All simulations should have the same AWH free-energy update interval")); + } + } + + /* Check the point sizes. This is a sufficient condition for running + * as shared multi-sim run. No physics checks are performed here. + */ + for (int b = 0; b < awhParams.numBias; b++) + { + if (awhParams.awhBiasParams[b].shareGroup > 0) + { + std::vector pointSizes(numSim); + pointSizes[multiSimComm->sim] = pointSize[b]; + gmx_sumli_sim(pointSizes.size(), pointSizes.data(), multiSimComm); + for (int sim = 1; sim < numSim; sim++) + { + if (pointSizes[sim] != pointSizes[0]) + { + GMX_THROW(InvalidInputError(gmx::formatString("Shared AWH bias %d has different grid sizes in different simulations\n", b + 1))); + } + } + } + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/biassharing.h b/src/gromacs/awh/biassharing.h new file mode 100644 index 0000000000..19fdadc7d3 --- /dev/null +++ b/src/gromacs/awh/biassharing.h @@ -0,0 +1,84 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares functions to check bias sharing properties. + * + * This actual sharing of biases is currently implemeted in BiasState. + * + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_BIASSHARING_H +#define GMX_AWH_BIASSHARING_H + +#include + +#include + +struct gmx_multisim_t; + +namespace gmx +{ + +struct AwhParams; + +/*! \brief Returns if any bias is sharing within a simulation. + * + * \param[in] awhParams The AWH parameters. + */ +bool haveBiasSharingWithinSimulation(const AwhParams &awhParams); + +/*! \brief Checks if biases are compatible for sharing between simulations, throws if not. + * + * Should be called simultaneously on the master rank of every simulation. + * Note that this only checks for technical compatibility. It is up to + * the user to check that the sharing physically makes sense. + * Throws an exception when shared biases are not compatible. + * + * \param[in] awhParams The AWH parameters. + * \param[in] pointSize Vector of grid-point sizes for each bias. + * \param[in] multiSimComm Struct for multi-simulation communication. + */ +void biasesAreCompatibleForSharingBetweenSimulations(const AwhParams &awhParams, + const std::vector &pointSize, + const gmx_multisim_t *multiSimComm); + +} // namespace gmx + +#endif /* GMX_AWH_BIASSHARING_H */ diff --git a/src/gromacs/awh/biasstate.cpp b/src/gromacs/awh/biasstate.cpp new file mode 100644 index 0000000000..5cbf18a069 --- /dev/null +++ b/src/gromacs/awh/biasstate.cpp @@ -0,0 +1,1613 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the BiasState class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "biasstate.h" + +#include + +#include +#include +#include +#include + +#include + +#include "gromacs/fileio/gmxfio.h" +#include "gromacs/fileio/xvgr.h" +#include "gromacs/gmxlib/network.h" +#include "gromacs/math/utilities.h" +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/smalloc.h" +#include "gromacs/utility/stringutil.h" + +#include "grid.h" +#include "pointstate.h" + +namespace gmx +{ + +void BiasState::getPmf(std::vector *pmf) const +{ + /* The PMF is just the negative of the log of the sampled PMF histogram. + * Points with zero target weight are ignored, they will mostly contain noise. + */ + + pmf->resize(points_.size()); + + for (size_t i = 0; i < points_.size(); i++) + { + (*pmf)[i] = points_[i].inTargetRegion() ? -points_[i].logPmfSum() : GMX_FLOAT_MAX; + } +} + +namespace +{ + +/*! \brief + * Sum PMF over multiple simulations, when requested. + * + * \param[in,out] pointState The state of the points in the bias. + * \param[in] numSharedUpdate The number of biases sharing the histogram. + * \param[in] multiSimComm Struct for multi-simulation communication. + */ +void sumPmf(gmx::ArrayRef pointState, + int numSharedUpdate, + const gmx_multisim_t *multiSimComm) +{ + if (numSharedUpdate == 1) + { + return; + } + GMX_ASSERT(multiSimComm != nullptr && numSharedUpdate % multiSimComm->nsim == 0, "numSharedUpdate should be a multiple of multiSimComm->nsim"); + GMX_ASSERT(numSharedUpdate == multiSimComm->nsim, "Sharing within a simulation is not implemented (yet)"); + + std::vector buffer(pointState.size()); + + /* Need to temporarily exponentiate the log weights to sum over simulations */ + for (size_t i = 0; i < buffer.size(); i++) + { + buffer[i] = pointState[i].inTargetRegion() ? std::exp(static_cast(pointState[i].logPmfSum())) : 0; + } + + gmx_sumd_sim(buffer.size(), buffer.data(), multiSimComm); + + /* Take log again to get (non-normalized) PMF */ + double normFac = 1.0/numSharedUpdate; + for (size_t i = 0; i < pointState.size(); i++) + { + if (pointState[i].inTargetRegion()) + { + pointState[i].setLogPmfSum(-std::log(buffer[i]*normFac)); + } + } +} + +/*! \brief + * Find the minimum free energy value. + * + * \param[in] pointState The state of the points. + * \returns the minimum free energy value. + */ +double freeEnergyMinimumValue(gmx::ArrayRef pointState) +{ + double fMin = GMX_DOUBLE_MAX; + + for (auto const &ps : pointState) + { + if (ps.inTargetRegion() && ps.freeEnergy() < fMin) + { + fMin = ps.freeEnergy(); + } + } + + return fMin; +} + +/*! \brief + * Find the probability weight of a point given a coordinate value. + * + * The unnormalized weight is given by + * w(point|value) = exp(bias(point) - U(value,point)), + * where U is a harmonic umbrella potential. + * + * \param[in] dimParams The bias dimensions parameters + * \param[in] points The point state. + * \param[in] grid The grid. + * \param[in] pointIndex Point to evaluate probability weight for. + * \param[in] pointBias Bias for the point (as a log weight). + * \param[in] value Coordinate value. + * \returns the biased probability weight. + */ +double biasedWeightFromPoint(const std::vector &dimParams, + const std::vector &points, + const Grid &grid, + int pointIndex, + double pointBias, + const awh_dvec value) +{ + double weight = 0; + + /* Only points in the target reigon have non-zero weight */ + if (points[pointIndex].inTargetRegion()) + { + double logWeight = pointBias; + + /* Add potential for all parameter dimensions */ + for (size_t d = 0; d < dimParams.size(); d++) + { + double dev = getDeviationFromPointAlongGridAxis(grid, d, pointIndex, value[d]); + logWeight -= 0.5*dimParams[d].betak*dev*dev; + } + + weight = std::exp(logWeight); + } + + return weight; +} + +} // namespace + +void BiasState::calcConvolvedPmf(const std::vector &dimParams, + const Grid &grid, + std::vector *convolvedPmf) const +{ + size_t numPoints = grid.numPoints(); + + convolvedPmf->resize(numPoints); + + /* Get the PMF to convolve. */ + std::vector pmf; + getPmf(&pmf); + + for (size_t m = 0; m < numPoints; m++) + { + double freeEnergyWeights = 0; + const GridPoint &point = grid.point(m); + for (auto &neighbor : point.neighbor) + { + /* The negative PMF is a positive bias. */ + double biasNeighbor = -pmf[neighbor]; + + /* Add the convolved PMF weights for the neighbors of this point. + Note that this function only adds point within the target > 0 region. + Sum weights, take the logarithm last to get the free energy. */ + freeEnergyWeights += biasedWeightFromPoint(dimParams, points_, grid, + neighbor, biasNeighbor, + point.coordValue); + } + + GMX_RELEASE_ASSERT(freeEnergyWeights > 0, "Attempting to do log(<= 0) in AWH convolved PMF calculation."); + (*convolvedPmf)[m] = -std::log(static_cast(freeEnergyWeights)); + } +} + +namespace +{ + +/*! \brief + * Updates the target distribution for all points. + * + * The target distribution is always updated for all points + * at the same time. + * + * \param[in,out] pointState The state of all points. + * \param[in] params The bias parameters. + */ +void updateTargetDistribution(gmx::ArrayRef pointState, + const BiasParams ¶ms) +{ + double freeEnergyCutoff = 0; + if (params.eTarget == eawhtargetCUTOFF) + { + freeEnergyCutoff = freeEnergyMinimumValue(pointState) + params.freeEnergyCutoffInKT; + } + + double sumTarget = 0; + for (PointState &ps : pointState) + { + sumTarget += ps.updateTargetWeight(params, freeEnergyCutoff); + } + GMX_RELEASE_ASSERT(sumTarget > 0, "We should have a non-zero distribution"); + + /* Normalize to 1 */ + double invSum = 1.0/sumTarget; + for (PointState &ps : pointState) + { + ps.scaleTarget(invSum); + } +} + +/*! \brief + * Puts together a string describing a grid point. + * + * \param[in] grid The grid. + * \param[in] point Grid point index. + * \returns a string for the point. + */ +std::string gridPointValueString(const Grid &grid, int point) +{ + std::string pointString; + + pointString += "("; + + for (int d = 0; d < grid.numDimensions(); d++) + { + pointString += gmx::formatString("%g", grid.point(point).coordValue[d]); + if (d < grid.numDimensions() - 1) + { + pointString += ","; + } + else + { + pointString += ")"; + } + } + + return pointString; +} + +} // namespace + +int BiasState::warnForHistogramAnomalies(const Grid &grid, + int biasIndex, + double t, + FILE *fplog, + int maxNumWarnings) const +{ + const double maxHistogramRatio = 0.5; /* Tolerance for printing a warning about the histogram ratios */ + + /* Sum up the histograms and get their normalization */ + double sumVisits = 0; + double sumWeights = 0; + for (auto &pointState : points_) + { + if (pointState.inTargetRegion()) + { + sumVisits += pointState.numVisitsTot(); + sumWeights += pointState.weightSumTot(); + } + } + GMX_RELEASE_ASSERT(sumVisits > 0, "We should have visits"); + GMX_RELEASE_ASSERT(sumWeights > 0, "We should have weight"); + double invNormVisits = 1.0/sumVisits; + double invNormWeight = 1.0/sumWeights; + + /* Check all points for warnings */ + int numWarnings = 0; + size_t numPoints = grid.numPoints(); + for (size_t m = 0; m < numPoints; m++) + { + /* Skip points close to boundary or non-target region */ + const GridPoint &gridPoint = grid.point(m); + bool skipPoint = false; + for (size_t n = 0; (n < gridPoint.neighbor.size()) && !skipPoint; n++) + { + int neighbor = gridPoint.neighbor[n]; + skipPoint = !points_[neighbor].inTargetRegion(); + for (int d = 0; (d < grid.numDimensions()) && !skipPoint; d++) + { + const GridPoint &neighborPoint = grid.point(neighbor); + skipPoint = + neighborPoint.index[d] == 0 || + neighborPoint.index[d] == grid.axis(d).numPoints() - 1; + } + } + + /* Warn if the coordinate distribution is less than the target distribution with a certain fraction somewhere */ + const double relativeWeight = points_[m].weightSumTot()*invNormWeight; + const double relativeVisits = points_[m].numVisitsTot()*invNormVisits; + if (!skipPoint && + relativeVisits < relativeWeight*maxHistogramRatio) + { + std::string pointValueString = gridPointValueString(grid, m); + std::string warningMessage = + gmx::formatString("\nawh%d warning: " + "at t = %g ps the obtained coordinate distribution at coordinate value %s " + "is less than a fraction %g of the reference distribution at that point. " + "If you are not certain about your settings you might want to increase your pull force constant or " + "modify your sampling region.\n", + biasIndex + 1, t, pointValueString.c_str(), maxHistogramRatio); + gmx::TextLineWrapper wrapper; + wrapper.settings().setLineLength(c_linewidth); + fprintf(fplog, "%s", wrapper.wrapToString(warningMessage).c_str()); + + numWarnings++; + } + if (numWarnings >= maxNumWarnings) + { + break; + } + } + + return numWarnings; +} + +double BiasState::calcUmbrellaForceAndPotential(const std::vector &dimParams, + const Grid &grid, + int point, + awh_dvec force) const +{ + double potential = 0; + for (size_t d = 0; d < dimParams.size(); d++) + { + double deviation = getDeviationFromPointAlongGridAxis(grid, d, point, coordState_.coordValue()[d]); + + double k = dimParams[d].k; + + /* Force from harmonic potential 0.5*k*dev^2 */ + force[d] = -k*deviation; + potential += 0.5*k*deviation*deviation; + } + + return potential; +} + +void BiasState::calcConvolvedForce(const std::vector &dimParams, + const Grid &grid, + const std::vector &probWeightNeighbor, + awh_dvec force) const +{ + for (size_t d = 0; d < dimParams.size(); d++) + { + force[d] = 0; + } + + /* Only neighboring points have non-negligible contribution. */ + const std::vector &neighbor = grid.point(coordState_.gridpointIndex()).neighbor; + for (size_t n = 0; n < neighbor.size(); n++) + { + double weightNeighbor = probWeightNeighbor[n]; + int indexNeighbor = neighbor[n]; + + /* Get the umbrella force from this point. The returned potential is ignored here. */ + awh_dvec forceFromNeighbor; + calcUmbrellaForceAndPotential(dimParams, grid, indexNeighbor, + forceFromNeighbor); + + /* Add the weighted umbrella force to the convolved force. */ + for (size_t d = 0; d < dimParams.size(); d++) + { + force[d] += forceFromNeighbor[d]*weightNeighbor; + } + } +} + +double BiasState::moveUmbrella(const std::vector &dimParams, + const Grid &grid, + const std::vector &probWeightNeighbor, + awh_dvec biasForce, + gmx_int64_t step, + gmx_int64_t seed, + int indexSeed) +{ + /* Generate and set a new coordinate reference value */ + coordState_.sampleUmbrellaGridpoint(grid, coordState_.gridpointIndex(), probWeightNeighbor, step, seed, indexSeed); + + awh_dvec newForce; + double newPotential = + calcUmbrellaForceAndPotential(dimParams, grid, coordState_.umbrellaGridpoint(), newForce); + + /* A modification of the reference value at time t will lead to a different + force over t-dt/2 to t and over t to t+dt/2. For high switching rates + this means the force and velocity will change signs roughly as often. + To avoid any issues we take the average of the previous and new force + at steps when the reference value has been moved. E.g. if the ref. value + is set every step to (coord dvalue +/- delta) would give zero force. + */ + for (size_t d = 0; d < dimParams.size(); d++) + { + /* clang thinks newForce[d] can be garbage */ +#ifndef __clang_analyzer__ + /* Average of the current and new force */ + biasForce[d] = 0.5*(biasForce[d] + newForce[d]); +#endif + } + + return newPotential; +} + +namespace +{ + +/*! \brief + * Sets the histogram rescaling factors needed to control the histogram size. + * + * For sake of robustness, the reference weight histogram can grow at a rate + * different from the actual sampling rate. Typically this happens for a limited + * initial time, alternatively growth is scaled down by a constant factor for all + * times. Since the size of the reference histogram sets the size of the free + * energy update this should be reflected also in the PMF. Thus the PMF histogram + * needs to be rescaled too. + * + * This function should only be called by the bias update function or wrapped by a function that + * knows what scale factors should be applied when, e.g, + * getSkippedUpdateHistogramScaleFactors(). + * + * \param[in] params The bias parameters. + * \param[in] newHistogramSize New reference weight histogram size. + * \param[in] oldHistogramSize Previous reference weight histogram size (before adding new samples). + * \param[out] weightHistScaling Scaling factor for the reference weight histogram. + * \param[out] logPmfSumScaling Log of the scaling factor for the PMF histogram. + */ +void setHistogramUpdateScaleFactors(const BiasParams ¶ms, + double newHistogramSize, + double oldHistogramSize, + double *weightHistScaling, + double *logPmfSumScaling) +{ + + /* The two scaling factors below are slightly different (ignoring the log factor) because the + reference and the PMF histogram apply weight scaling differently. The weight histogram + applies is locally, i.e. each sample is scaled down meaning all samples get equal weight. + It is done this way because that is what target type local Boltzmann (for which + target = weight histogram) needs. In contrast, the PMF histogram is rescaled globally + by repeatedly scaling down the whole histogram. The reasons for doing it this way are: + 1) empirically this is necessary for converging the PMF; 2) since the extraction of + the PMF is theoretically only valid for a constant bias, new samples should get more + weight than old ones for which the bias is fluctuating more. */ + *weightHistScaling = newHistogramSize/(oldHistogramSize + params.updateWeight*params.localWeightScaling); + *logPmfSumScaling = std::log(newHistogramSize/(oldHistogramSize + params.updateWeight)); +} + +} // namespace + +void BiasState::getSkippedUpdateHistogramScaleFactors(const BiasParams ¶ms, + double *weightHistScaling, + double *logPmfSumScaling) const +{ + GMX_ASSERT(params.skipUpdates(), "Calling function for skipped updates when skipping updates is not allowed"); + + if (inInitialStage()) + { + /* In between global updates the reference histogram size is kept constant so we trivially know what the + histogram size was at the time of the skipped update. */ + double histogramSize = histogramSize_.histogramSize(); + setHistogramUpdateScaleFactors(params, histogramSize, histogramSize, + weightHistScaling, logPmfSumScaling); + } + else + { + /* In the final stage, the reference histogram grows at the sampling rate which gives trivial scale factors. */ + *weightHistScaling = 1; + *logPmfSumScaling = 0; + } +} + +void BiasState::doSkippedUpdatesForAllPoints(const BiasParams ¶ms) +{ + double weightHistScaling; + double logPmfsumScaling; + + getSkippedUpdateHistogramScaleFactors(params, &weightHistScaling, &logPmfsumScaling); + + for (auto &pointState : points_) + { + bool didUpdate = pointState.performPreviouslySkippedUpdates(params, histogramSize_.numUpdates(), weightHistScaling, logPmfsumScaling); + + /* Update the bias for this point only if there were skipped updates in the past to avoid calculating the log unneccessarily */ + if (didUpdate) + { + pointState.updateBias(); + } + } +} + +void BiasState::doSkippedUpdatesInNeighborhood(const BiasParams ¶ms, + const Grid &grid) +{ + double weightHistScaling; + double logPmfsumScaling; + + getSkippedUpdateHistogramScaleFactors(params, &weightHistScaling, &logPmfsumScaling); + + /* For each neighbor point of the center point, refresh its state by adding the results of all past, skipped updates. */ + const std::vector &neighbors = grid.point(coordState_.gridpointIndex()).neighbor; + for (auto &neighbor : neighbors) + { + bool didUpdate = points_[neighbor].performPreviouslySkippedUpdates(params, histogramSize_.numUpdates(), weightHistScaling, logPmfsumScaling); + + if (didUpdate) + { + points_[neighbor].updateBias(); + } + } +} + +namespace +{ + +/*! \brief + * Merge update lists from multiple sharing simulations. + * + * \param[in,out] updateList Update list for this simulation (assumed >= npoints long). + * \param[in] numPoints Total number of points. + * \param[in] multiSimComm Struct for multi-simulation communication. + */ +void mergeSharedUpdateLists(std::vector *updateList, + int numPoints, + const gmx_multisim_t *multiSimComm) +{ + std::vector numUpdatesOfPoint; + + /* Flag the update points of this sim. + TODO: we can probably avoid allocating this array and just use the input array. */ + numUpdatesOfPoint.resize(numPoints, 0); + for (auto &pointIndex : *updateList) + { + numUpdatesOfPoint[pointIndex] = 1; + } + + /* Sum over the sims to get all the flagged points */ + gmx_sumi_sim(numPoints, numUpdatesOfPoint.data(), multiSimComm); + + /* Collect the indices of the flagged points in place. The resulting array will be the merged update list.*/ + updateList->clear(); + for (int m = 0; m < numPoints; m++) + { + if (numUpdatesOfPoint[m] > 0) + { + updateList->push_back(m); + } + } +} + +/*! \brief + * Generate an update list of points sampled since the last update. + * + * \param[in] grid The AWH bias. + * \param[in] points The point state. + * \param[in] originUpdatelist The origin of the rectangular region that has been sampled since last update. + * \param[in] endUpdatelist The end of the rectangular that has been sampled since last update. + * \param[in,out] updateList Local update list to set (assumed >= npoints long). + */ +void makeLocalUpdateList(const Grid &grid, + const std::vector &points, + const awh_ivec originUpdatelist, + const awh_ivec endUpdatelist, + std::vector *updateList) +{ + awh_ivec origin; + awh_ivec numPoints; + + /* Define the update search grid */ + for (int d = 0; d < grid.numDimensions(); d++) + { + origin[d] = originUpdatelist[d]; + numPoints[d] = endUpdatelist[d] - originUpdatelist[d] + 1; + + /* Because the end_updatelist is unwrapped it can be > (npoints - 1) so that numPoints can be > npoints in grid. + This helps for calculating the distance/number of points but should be removed and fixed when the way of + updating origin/end updatelist is changed (see sampleProbabilityWeights). */ + numPoints[d] = std::min(grid.axis(d).numPoints(), numPoints[d]); + } + + /* Make the update list */ + updateList->clear(); + int pointIndex = -1; + bool pointExists = true; + while (pointExists) + { + pointExists = advancePointInSubgrid(grid, origin, numPoints, &pointIndex); + + if (pointExists && points[pointIndex].inTargetRegion()) + { + updateList->push_back(pointIndex); + } + } +} + +} // namespace + +void BiasState::resetLocalUpdateRange(const Grid &grid) +{ + const int gridpointIndex = coordState_.gridpointIndex(); + for (int d = 0; d < grid.numDimensions(); d++) + { + /* This gives the minimum range consisting only of the current closest point. */ + originUpdatelist_[d] = grid.point(gridpointIndex).index[d]; + endUpdatelist_[d] = grid.point(gridpointIndex).index[d]; + } +} + +namespace +{ + +/*! \brief + * Add partial histograms (accumulating between updates) to accumulating histograms. + * + * \param[in,out] pointState The state of the points in the bias. + * \param[in,out] weightSumCovering The weights for checking covering. + * \param[in] numSharedUpdate The number of biases sharing the histrogram. + * \param[in] multiSimComm Struct for multi-simulation communication. + * \param[in] localUpdateList List of points with data. + */ +void sumHistograms(gmx::ArrayRef pointState, + gmx::ArrayRef weightSumCovering, + int numSharedUpdate, + const gmx_multisim_t *multiSimComm, + const std::vector &localUpdateList) +{ + /* The covering checking histograms are added before summing over simulations, so that the weights from different + simulations are kept distinguishable. */ + for (int globalIndex : localUpdateList) + { + weightSumCovering[globalIndex] += + pointState[globalIndex].weightSumIteration(); + } + + /* Sum histograms over multiple simulations if needed. */ + if (numSharedUpdate > 1) + { + GMX_ASSERT(numSharedUpdate == multiSimComm->nsim, "Sharing within a simulation is not implemented (yet)"); + + /* Collect the weights and counts in linear arrays to be able to use gmx_sumd_sim. */ + std::vector weightSum; + std::vector coordVisits; + + weightSum.resize(localUpdateList.size()); + coordVisits.resize(localUpdateList.size()); + + for (size_t localIndex = 0; localIndex < localUpdateList.size(); localIndex++) + { + const PointState &ps = pointState[localUpdateList[localIndex]]; + + weightSum[localIndex] = ps.weightSumIteration(); + coordVisits[localIndex] = ps.numVisitsIteration(); + } + + gmx_sumd_sim(weightSum.size(), weightSum.data(), multiSimComm); + gmx_sumd_sim(coordVisits.size(), coordVisits.data(), multiSimComm); + + /* Transfer back the result */ + for (size_t localIndex = 0; localIndex < localUpdateList.size(); localIndex++) + { + PointState &ps = pointState[localUpdateList[localIndex]]; + + ps.setPartialWeightAndCount(weightSum[localIndex], + coordVisits[localIndex]); + } + } + + /* Now add the partial counts and weights to the accumulating histograms. + Note: we still need to use the weights for the update so we wait + with resetting them until the end of the update. */ + for (int globalIndex : localUpdateList) + { + pointState[globalIndex].addPartialWeightAndCount(); + } +} + +/*! \brief + * Label points along an axis as covered or not. + * + * A point is covered if it is surrounded by visited points up to a radius = coverRadius. + * + * \param[in] visited Visited? For each point. + * \param[in] checkCovering Check for covering? For each point. + * \param[in] numPoints The number of grid points along this dimension. + * \param[in] period Period in number of points. + * \param[in] coverRadius Cover radius, in points, needed for defining a point as covered. + * \param[in,out] covered In this array elements are 1 for covered points and 0 for non-covered points, this routine assumes that \p covered has at least size \p numPoints. + */ +void labelCoveredPoints(const std::vector &visited, + const std::vector &checkCovering, + int numPoints, + int period, + int coverRadius, + gmx::ArrayRef covered) +{ + GMX_ASSERT(covered.size() >= static_cast(numPoints), "covered should be at least as large as the grid"); + + bool haveFirstNotVisited = false; + int firstNotVisited = -1; + int notVisitedLow = -1; + int notVisitedHigh = -1; + + for (int n = 0; n < numPoints; n++) + { + if (checkCovering[n] && !visited[n]) + { + if (!haveFirstNotVisited) + { + notVisitedLow = n; + firstNotVisited = n; + haveFirstNotVisited = true; + } + else + { + notVisitedHigh = n; + + /* Have now an interval I = [notVisitedLow,notVisitedHigh] of visited points bounded + by unvisited points. The unvisted end points affect the coveredness of the visited + with a reach equal to the cover radius. */ + int notCoveredLow = notVisitedLow + coverRadius; + int notCoveredHigh = notVisitedHigh - coverRadius; + for (int i = notVisitedLow; i <= notVisitedHigh; i++) + { + covered[i] = (i > notCoveredLow) && (i < notCoveredHigh); + } + + /* Find a new interval to set covering for. Make the notVisitedHigh of this interval the + notVisitedLow of the next. */ + notVisitedLow = notVisitedHigh; + } + } + } + + /* Have labelled all the internal points. Now take care of the boundary regions. */ + if (!haveFirstNotVisited) + { + /* No non-visited points <=> all points visited => all points covered. */ + + for (int n = 0; n < numPoints; n++) + { + covered[n] = 1; + } + } + else + { + int lastNotVisited = notVisitedLow; + + /* For periodic boundaries, non-visited points can influence points + on the other side of the boundary so we need to wrap around. */ + + /* Lower end. For periodic boundaries the last upper end not visited point becomes the low-end not visited point. + For non-periodic boundaries there is no lower end point so a dummy value is used. */ + int notVisitedHigh = firstNotVisited; + int notVisitedLow = period > 0 ? (lastNotVisited - period) : -(coverRadius + 1); + + int notCoveredLow = notVisitedLow + coverRadius; + int notCoveredHigh = notVisitedHigh - coverRadius; + + for (int i = 0; i <= notVisitedHigh; i++) + { + /* For non-periodic boundaries notCoveredLow = -1 will impose no restriction. */ + covered[i] = (i > notCoveredLow) && (i < notCoveredHigh); + } + + /* Upper end. Same as for lower end but in the other direction. */ + notVisitedHigh = period > 0 ? (firstNotVisited + period) : (numPoints + coverRadius); + notVisitedLow = lastNotVisited; + + notCoveredLow = notVisitedLow + coverRadius; + notCoveredHigh = notVisitedHigh - coverRadius; + + for (int i = notVisitedLow; i <= numPoints - 1; i++) + { + /* For non-periodic boundaries notCoveredHigh = numPoints will impose no restriction. */ + covered[i] = (i > notCoveredLow) && (i < notCoveredHigh); + } + } +} + +} // namespace + +bool BiasState::isSamplingRegionCovered(const BiasParams ¶ms, + const std::vector &dimParams, + const Grid &grid, + const gmx_multisim_t *multiSimComm) const +{ + /* Allocate and initialize arrays: one for checking visits along each dimension, + one for keeping track of which points to check and one for the covered points. + Possibly these could be kept as AWH variables to avoid these allocations. */ + struct CheckDim + { + std::vector visited; + std::vector checkCovering; + // We use int for the covering array since we might use gmx_sumi_sim. + std::vector covered; + }; + + std::vector checkDim; + checkDim.resize(grid.numDimensions()); + + for (int d = 0; d < grid.numDimensions(); d++) + { + const size_t numPoints = grid.axis(d).numPoints(); + checkDim[d].visited.resize(numPoints, false); + checkDim[d].checkCovering.resize(numPoints, false); + checkDim[d].covered.resize(numPoints, 0); + } + + /* Set visited points along each dimension and which points should be checked for covering. + Specifically, points above the free energy cutoff (if there is one) or points outside + of the target region are ignored. */ + + /* Set the free energy cutoff */ + double maxFreeEnergy = GMX_DOUBLE_MAX; + + if (params.eTarget == eawhtargetCUTOFF) + { + maxFreeEnergy = freeEnergyMinimumValue(points_) + params.freeEnergyCutoffInKT; + } + + /* Set the threshold weight for a point to be considered visited. */ + double weightThreshold = 1; + for (int d = 0; d < grid.numDimensions(); d++) + { + weightThreshold *= grid.axis(d).spacing()*std::sqrt(dimParams[d].betak*0.5*M_1_PI); + } + + /* Project the sampling weights onto each dimension */ + for (size_t m = 0; m < grid.numPoints(); m++) + { + const PointState &pointState = points_[m]; + + for (int d = 0; d < grid.numDimensions(); d++) + { + int n = grid.point(m).index[d]; + + /* Is visited if it was already visited or if there is enough weight at the current point */ + checkDim[d].visited[n] = checkDim[d].visited[n] || (weightSumCovering_[m] > weightThreshold); + + /* Check for covering if there is at least point in this slice that is in the target region and within the cutoff */ + checkDim[d].checkCovering[n] = checkDim[d].checkCovering[n] || (pointState.inTargetRegion() && pointState.freeEnergy() < maxFreeEnergy); + } + } + + /* Label each point along each dimension as covered or not. */ + for (int d = 0; d < grid.numDimensions(); d++) + { + labelCoveredPoints(checkDim[d].visited, checkDim[d].checkCovering, grid.axis(d).numPoints(), grid.axis(d).numPointsInPeriod(), + params.coverRadius()[d], checkDim[d].covered); + } + + /* Now check for global covering. Each dimension needs to be covered separately. + A dimension is covered if each point is covered. Multiple simulations collectively + cover the points, i.e. a point is covered if any of the simulations covered it. + However, visited points are not shared, i.e. if a point is covered or not is + determined by the visits of a single simulation. In general the covering criterion is + all points covered => all points are surrounded by visited points up to a radius = coverRadius. + For 1 simulation, all points covered <=> all points visited. For multiple simulations + however, all points visited collectively !=> all points covered, except for coverRadius = 0. + In the limit of large coverRadius, all points covered => all points visited by at least one + simulation (since no point will be covered until all points have been visited by a + single simulation). Basically coverRadius sets how much "connectedness" (or mixing) a point + needs with surrounding points before sharing covering information with other simulations. */ + + /* Communicate the covered points between sharing simulations if needed. */ + if (params.numSharedUpdate > 1) + { + /* For multiple dimensions this may not be the best way to do it. */ + for (int d = 0; d < grid.numDimensions(); d++) + { + gmx_sumi_sim(grid.axis(d).numPoints(), checkDim[d].covered.data(), multiSimComm); + } + } + + /* Now check if for each dimension all points are covered. Break if not true. */ + bool allPointsCovered = true; + for (int d = 0; d < grid.numDimensions() && allPointsCovered; d++) + { + for (int n = 0; n < grid.axis(d).numPoints() && allPointsCovered; n++) + { + allPointsCovered = checkDim[d].covered[n]; + } + } + + return allPointsCovered; +} + +/*! \brief + * Normalizes the free energy and PMF sum. + * + * \param[in] pointState The state of the points. + */ +static void normalizeFreeEnergyAndPmfSum(std::vector *pointState) +{ + double minF = freeEnergyMinimumValue(*pointState); + + for (PointState &ps : *pointState) + { + ps.normalizeFreeEnergyAndPmfSum(minF); + } +} + +void BiasState::updateFreeEnergyAndAddSamplesToHistogram(const std::vector &dimParams, + const Grid &grid, + const BiasParams ¶ms, + const gmx_multisim_t *multiSimComm, + double t, + gmx_int64_t step, + FILE *fplog, + std::vector *updateList) +{ + /* Note hat updateList is only used in this scope and is always + * re-initialized. We do not use a local vector, because that would + * cause reallocation every time this funtion is called and the vector + * can be the size of the whole grid. + */ + + /* Make a list of all local points, i.e. those that could have been touched since + the last update. These are the points needed for summing histograms below + (non-local points only add zeros). For local updates, this will also be the + final update list. */ + makeLocalUpdateList(grid, points_, originUpdatelist_, endUpdatelist_, + updateList); + if (params.numSharedUpdate > 1) + { + mergeSharedUpdateLists(updateList, points_.size(), multiSimComm); + } + + /* Reset the range for the next update */ + resetLocalUpdateRange(grid); + + /* Add samples to histograms for all local points and sync simulations if needed */ + sumHistograms(points_, weightSumCovering_, + params.numSharedUpdate, multiSimComm, *updateList); + + sumPmf(points_, params.numSharedUpdate, multiSimComm); + + /* Renormalize the free energy if values are too large. */ + bool needToNormalizeFreeEnergy = false; + for (int &globalIndex : *updateList) + { + /* We want to keep the absolute value of the free energies to be less c_largePositiveExponent + to be able to safely pass these values to exp(). The check below ensures this as long as + the free energy values grow less than 0.5*c_largePositiveExponent in a return time to this + neighborhood. For reasonable update sizes it's unlikely that this requirement would be + broken. */ + if (std::abs(points_[globalIndex].freeEnergy()) > 0.5*c_largePositiveExponent) + { + needToNormalizeFreeEnergy = true; + break; + } + } + + /* Update target distribution? */ + bool needToUpdateTargetDistribution = + (params.eTarget != eawhtargetCONSTANT && + params.isUpdateTargetStep(step)); + + /* In the initial stage, the histogram grows dynamically as a function of the number of coverings. */ + bool detectedCovering = false; + if (inInitialStage()) + { + detectedCovering = (params.isCheckStep(points_.size(), step) && + isSamplingRegionCovered(params, dimParams, grid, multiSimComm)); + } + + /* The weighthistogram size after this update. */ + double newHistogramSize = histogramSize_.newHistogramSize(params, t, detectedCovering, points_, weightSumCovering_, fplog); + + /* Make the update list. Usually we try to only update local points, + * but if the update has non-trivial or non-deterministic effects + * on non-local points a global update is needed. This is the case when: + * 1) a covering occurred in the initial stage, leading to non-trivial + * histogram rescaling factors; or + * 2) the target distribution will be updated, since we don't make any + * assumption on its form; or + * 3) the AWH parameters are such that we never attempt to skip non-local + * updates; or + * 4) the free energy values have grown so large that a renormalization + * is needed. + */ + if (needToUpdateTargetDistribution || detectedCovering || !params.skipUpdates() || needToNormalizeFreeEnergy) + { + /* Global update, just add all points. */ + updateList->clear(); + for (size_t m = 0; m < points_.size(); m++) + { + if (points_[m].inTargetRegion()) + { + updateList->push_back(m); + } + } + } + + /* Set histogram scale factors. */ + double weightHistScalingSkipped = 0; + double logPmfsumScalingSkipped = 0; + if (params.skipUpdates()) + { + getSkippedUpdateHistogramScaleFactors(params, &weightHistScalingSkipped, &logPmfsumScalingSkipped); + } + double weightHistScalingNew; + double logPmfsumScalingNew; + setHistogramUpdateScaleFactors(params, newHistogramSize, histogramSize_.histogramSize(), + &weightHistScalingNew, &logPmfsumScalingNew); + + /* Update free energy and reference weight histogram for points in the update list. */ + for (int pointIndex : *updateList) + { + PointState *pointStateToUpdate = &points_[pointIndex]; + + /* Do updates from previous update steps that were skipped because this point was at that time non-local. */ + if (params.skipUpdates()) + { + pointStateToUpdate->performPreviouslySkippedUpdates(params, histogramSize_.numUpdates(), weightHistScalingSkipped, logPmfsumScalingSkipped); + } + + /* Now do an update with new sampling data. */ + pointStateToUpdate->updateWithNewSampling(params, histogramSize_.numUpdates(), weightHistScalingNew, logPmfsumScalingNew); + } + + /* Only update the histogram size after we are done with the local point updates */ + histogramSize_.setHistogramSize(newHistogramSize, weightHistScalingNew); + + if (needToNormalizeFreeEnergy) + { + normalizeFreeEnergyAndPmfSum(&points_); + } + + if (needToUpdateTargetDistribution) + { + /* The target distribution is always updated for all points at once. */ + updateTargetDistribution(points_, params); + } + + /* Update the bias. The bias is updated separately and last since it simply a function of + the free energy and the target distribution and we want to avoid doing extra work. */ + for (int pointIndex : *updateList) + { + points_[pointIndex].updateBias(); + } + + /* Increase the update counter. */ + histogramSize_.incrementNumUpdates(); +} + +double BiasState::updateProbabilityWeightsAndConvolvedBias(const std::vector &dimParams, + const Grid &grid, + std::vector *weight) const +{ + /* Only neighbors of the current coordinate value will have a non-negligible chance of getting sampled */ + const std::vector &neighbors = grid.point(coordState_.gridpointIndex()).neighbor; + + weight->resize(neighbors.size()); + + double weightSum = 0; + for (size_t n = 0; n < neighbors.size(); n++) + { + int neighbor = neighbors[n]; + (*weight)[n] = biasedWeightFromPoint(dimParams, points_, grid, + neighbor, points_[neighbor].bias(), + coordState_.coordValue()); + weightSum += (*weight)[n]; + } + GMX_RELEASE_ASSERT(weightSum > 0, "zero probability weight when updating AWH probability weights."); + + /* Normalize probabilities to sum to 1 */ + double invWeightSum = 1/weightSum; + for (double &w : *weight) + { + w *= invWeightSum; + } + + /* Return the convolved bias */ + return std::log(weightSum); +} + +double BiasState::calcConvolvedBias(const std::vector &dimParams, + const Grid &grid, + const awh_dvec &coordValue) const +{ + int point = grid.nearestIndex(coordValue); + const GridPoint &gridPoint = grid.point(point); + + /* Sum the probability weights from the neighborhood of the given point */ + double weightSum = 0; + for (int neighbor : gridPoint.neighbor) + { + weightSum += biasedWeightFromPoint(dimParams, points_, grid, + neighbor, points_[neighbor].bias(), + coordValue); + } + + /* Returns -GMX_DOUBLE_MAX if no neighboring points were in the target region. */ + return (weightSum > 0) ? std::log(weightSum) : -GMX_DOUBLE_MAX; +} + +void BiasState::sampleProbabilityWeights(const Grid &grid, + const std::vector &probWeightNeighbor) +{ + const std::vector &neighbor = grid.point(coordState_.gridpointIndex()).neighbor; + + /* Save weights for next update */ + for (size_t n = 0; n < neighbor.size(); n++) + { + points_[neighbor[n]].increaseWeightSumIteration(probWeightNeighbor[n]); + } + + /* Update the local update range. Two corner points define this rectangular + * domain. We need to choose two new corner points such that the new domain + * contains both the old update range and the current neighborhood. + * In the simplest case when an update is performed every sample, + * the update range would simply equal the current neighborhood. + */ + int neighborStart = neighbor[0]; + int neighborLast = neighbor[neighbor.size() - 1]; + for (int d = 0; d < grid.numDimensions(); d++) + { + int origin = grid.point(neighborStart).index[d]; + int last = grid.point(neighborLast).index[d]; + + if (origin > last) + { + /* Unwrap if wrapped around the boundary (only happens for periodic + * boundaries). This has been already for the stored index interval. + */ + /* TODO: what we want to do is to find the smallest the update + * interval that contains all points that need to be updated. + * This amounts to combining two intervals, the current + * [origin, end] update interval and the new touched neighborhood + * into a new interval that contains all points from both the old + * intervals. + * + * For periodic boundaries it becomes slightly more complicated + * than for closed boundaries because then it needs not be + * true that origin < end (so one can't simply relate the origin/end + * in the min()/max() below). The strategy here is to choose the + * origin closest to a reference point (index 0) and then unwrap + * the end index if needed and choose the largest end index. + * This ensures that both intervals are in the new interval + * but it's not necessarily the smallest. + * Currently we solve this by going through each possibility + * and checking them. + */ + last += grid.axis(d).numPointsInPeriod(); + } + + originUpdatelist_[d] = std::min(originUpdatelist_[d], origin); + endUpdatelist_[d] = std::max(endUpdatelist_[d], last); + } +} + +void BiasState::sampleCoordAndPmf(const Grid &grid, + const std::vector &probWeightNeighbor, + double convolvedBias) +{ + /* Sampling-based deconvolution extracting the PMF. + * Update the PMF histogram with the current coordinate value. + * + * Because of the finite width of the harmonic potential, the free energy + * defined for each coordinate point does not exactly equal that of the + * actual coordinate, the PMF. However, the PMF can be estimated by applying + * the relation exp(-PMF) = exp(-bias_convolved)*P_biased/Z, i.e. by keeping a + * reweighted histogram of the coordinate value. Strictly, this relies on + * the unknown normalization constant Z being either constant or known. Here, + * neither is true except in the long simulation time limit. Empirically however, + * it works (mainly because how the PMF histogram is rescaled). + */ + + /* Only save coordinate data that is in range (the given index is always + * in range even if the coordinate value is not). + */ + if (grid.covers(coordState_.coordValue())) + { + /* Save PMF sum and keep a histogram of the sampled coordinate values */ + points_[coordState_.gridpointIndex()].samplePmf(convolvedBias); + } + + /* Save probability weights for the update */ + sampleProbabilityWeights(grid, probWeightNeighbor); +} + +void BiasState::initHistoryFromState(AwhBiasHistory *biasHistory) const +{ + biasHistory->pointState.resize(points_.size()); +} + +void BiasState::updateHistory(AwhBiasHistory *biasHistory, + const Grid &grid) const +{ + GMX_RELEASE_ASSERT(biasHistory->pointState.size() == points_.size(), "The AWH history setup does not match the AWH state."); + + AwhBiasStateHistory *stateHistory = &biasHistory->state; + stateHistory->umbrellaGridpoint = coordState_.umbrellaGridpoint(); + + for (size_t m = 0; m < biasHistory->pointState.size(); m++) + { + AwhPointStateHistory *psh = &biasHistory->pointState[m]; + + points_[m].storeState(psh); + + psh->weightsum_covering = weightSumCovering_[m]; + } + + histogramSize_.storeState(stateHistory); + + stateHistory->origin_index_updatelist = multiDimGridIndexToLinear(grid, + originUpdatelist_); + stateHistory->end_index_updatelist = multiDimGridIndexToLinear(grid, + endUpdatelist_); +} + +void BiasState::restoreFromHistory(const AwhBiasHistory &biasHistory, + const Grid &grid) +{ + const AwhBiasStateHistory &stateHistory = biasHistory.state; + + coordState_.restoreFromHistory(stateHistory); + + if (biasHistory.pointState.size() != points_.size()) + { + GMX_THROW(InvalidInputError("Bias grid size in checkpoint and simulation do not match. Likely you provided a checkpoint from a different simulation.")); + } + for (size_t m = 0; m < points_.size(); m++) + { + points_[m].setFromHistory(biasHistory.pointState[m]); + } + + for (size_t m = 0; m < weightSumCovering_.size(); m++) + { + weightSumCovering_[m] = biasHistory.pointState[m].weightsum_covering; + } + + histogramSize_.restoreFromHistory(stateHistory); + + linearGridindexToMultiDim(grid, stateHistory.origin_index_updatelist, originUpdatelist_); + linearGridindexToMultiDim(grid, stateHistory.end_index_updatelist, endUpdatelist_); +} + +void BiasState::broadcast(const t_commrec *commRecord) +{ + gmx_bcast(sizeof(coordState_), &coordState_, commRecord); + + gmx_bcast(points_.size()*sizeof(PointState), points_.data(), commRecord); + + gmx_bcast(weightSumCovering_.size()*sizeof(double), weightSumCovering_.data(), commRecord); + + gmx_bcast(sizeof(histogramSize_), &histogramSize_, commRecord); +} + +void BiasState::setFreeEnergyToConvolvedPmf(const std::vector &dimParams, + const Grid &grid) +{ + std::vector convolvedPmf; + + calcConvolvedPmf(dimParams, grid, &convolvedPmf); + + for (size_t m = 0; m < points_.size(); m++) + { + points_[m].setFreeEnergy(convolvedPmf[m]); + } +} + +/*! \brief + * Count trailing data rows containing only zeros. + * + * \param[in] data 2D data array. + * \param[in] numRows Number of rows in array. + * \param[in] numColumns Number of cols in array. + * \returns the number of trailing zero rows. + */ +static int countTrailingZeroRows(const double* const *data, + int numRows, + int numColumns) +{ + int numZeroRows = 0; + for (int m = numRows - 1; m >= 0; m--) + { + bool rowIsZero = true; + for (int d = 0; d < numColumns; d++) + { + if (data[d][m] != 0) + { + rowIsZero = false; + break; + } + } + + if (!rowIsZero) + { + /* At a row with non-zero data */ + break; + } + else + { + /* Still at a zero data row, keep checking rows higher up. */ + numZeroRows++; + } + } + + return numZeroRows; +} + +/*! \brief + * Initializes the PMF and target with data read from an input table. + * + * \param[in] dimParams The dimension parameters. + * \param[in] grid The grid. + * \param[in] filename The filename to read PMF and target from. + * \param[in] numBias Number of biases. + * \param[in] biasIndex The index of the bias. + * \param[in,out] pointState The state of the points in this bias. + */ +static void readUserPmfAndTargetDistribution(const std::vector &dimParams, + const Grid &grid, + const std::string &filename, + int numBias, + int biasIndex, + std::vector *pointState) +{ + /* Read the PMF and target distribution. + From the PMF, the convolved PMF, or the reference value free energy, can be calculated + base on the force constant. The free energy and target together determine the bias. + */ + std::string filenameModified(filename); + if (numBias > 1) + { + size_t n = filenameModified.rfind("."); + GMX_RELEASE_ASSERT(n != std::string::npos, "The filename should contain an extension starting with ."); + filenameModified.insert(n, formatString("%d", biasIndex)); + } + + std::string correctFormatMessage = + formatString("%s is expected in the following format. " + "The first ndim column(s) should contain the coordinate values for each point, " + "each column containing values of one dimension (in ascending order). " + "For a multidimensional coordinate, points should be listed " + "in the order obtained by traversing lower dimensions first. " + "E.g. for two-dimensional grid of size nxn: " + "(1, 1), (1, 2),..., (1, n), (2, 1), (2, 2), ..., , (n, n - 1), (n, n). " + "Column ndim + 1 should contain the PMF value for each coordinate value. " + "The target distribution values should be in column ndim + 2 or column ndim + 5. " + "Make sure the input file ends with a new line but has no trailing new lines.", + filename.c_str()); + gmx::TextLineWrapper wrapper; + wrapper.settings().setLineLength(c_linewidth); + correctFormatMessage = wrapper.wrapToString(correctFormatMessage).c_str(); + + double **data; + int numColumns; + int numRows = read_xvg(filenameModified.c_str(), &data, &numColumns); + + /* Check basic data properties here. Grid takes care of more complicated things. */ + + if (numRows <= 0) + { + std::string mesg = gmx::formatString("%s is empty!.\n\n%s", filename.c_str(), correctFormatMessage.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + + /* Less than 2 points is not useful for PMF or target. */ + if (numRows < 2) + { + std::string mesg = + gmx::formatString("%s contains too few data points (%d)." + "The minimum number of points is 2.", + filename.c_str(), numRows); + GMX_THROW(InvalidInputError(mesg)); + } + + /* Make sure there are enough columns of data. + + Two formats are allowed. Either with columns {coords, PMF, target} or + {coords, PMF, x, y, z, target, ...}. The latter format is allowed since that + is how AWH output is written (x, y, z being other AWH variables). For this format, + trailing columns are ignored. + */ + int columnIndexTarget; + int numColumnsMin = dimParams.size() + 2; + int columnIndexPmf = dimParams.size(); + if (numColumns == numColumnsMin) + { + columnIndexTarget = columnIndexPmf + 1; + } + else + { + columnIndexTarget = columnIndexPmf + 4; + } + + if (numColumns < numColumnsMin) + { + std::string mesg = + gmx::formatString("The number of columns in %s (%d) should be at least %d." + "\n\n%s", + filename.c_str(), correctFormatMessage.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + + /* read_xvg can give trailing zero data rows for trailing new lines in the input. We allow 1 zero row, + since this could be real data. But multiple trailing zero rows cannot correspond to valid data. */ + int numZeroRows = countTrailingZeroRows(data, numRows, numColumns); + if (numZeroRows > 1) + { + std::string mesg = gmx::formatString("Found %d trailing zero data rows in %s. Please remove trailing empty lines and try again.", + numZeroRows, filename.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + + /* Convert from user units to internal units before sending the data of to grid. */ + for (size_t d = 0; d < dimParams.size(); d++) + { + double scalingFactor = dimParams[d].scaleUserInputToInternal(1); + if (scalingFactor == 1) + { + continue; + } + for (size_t m = 0; m < pointState->size(); m++) + { + data[d][m] *= scalingFactor; + } + } + + /* Get a data point for each AWH grid point so that they all get data. */ + std::vector gridIndexToDataIndex(grid.numPoints()); + mapGridToDataGrid(&gridIndexToDataIndex, data, numRows, filename, grid, correctFormatMessage); + + /* Extract the data for each grid point. + * We check if the target distribution is zero for all points. + */ + bool targetDistributionIsZero = true; + for (size_t m = 0; m < pointState->size(); m++) + { + (*pointState)[m].setLogPmfSum(-data[columnIndexPmf][gridIndexToDataIndex[m]]); + double target = data[columnIndexTarget][gridIndexToDataIndex[m]]; + + /* Check if the values are allowed. */ + if (target < 0) + { + std::string mesg = gmx::formatString("Target distribution weight at point %d (%g) in %s is negative.", + m, target, filename.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + if (target > 0) + { + targetDistributionIsZero = false; + } + (*pointState)[m].setTargetConstantWeight(target); + } + + if (targetDistributionIsZero) + { + std::string mesg = gmx::formatString("The target weights given in column %d in %s are all 0", + filename.c_str(), columnIndexTarget); + GMX_THROW(InvalidInputError(mesg)); + } + + /* Free the arrays. */ + for (int m = 0; m < numColumns; m++) + { + sfree(data[m]); + } + sfree(data); +} + +void BiasState::normalizePmf(int numSharingSims) +{ + /* The normalization of the PMF estimate matters because it determines how big effect the next sample has. + Approximately (for large enough force constant) we should have: + sum_x(exp(-pmf(x)) = nsamples*sum_xref(exp(-f(xref)). + */ + + /* Calculate the normalization factor, i.e. divide by the pmf sum, multiply by the number of samples and the f sum */ + double expSumPmf = 0; + double expSumF = 0; + for (const PointState &pointState : points_) + { + if (pointState.inTargetRegion()) + { + expSumPmf += std::exp( pointState.logPmfSum()); + expSumF += std::exp(-pointState.freeEnergy()); + } + } + double numSamples = histogramSize_.histogramSize()/numSharingSims; + + /* Renormalize */ + double logRenorm = std::log(numSamples*expSumF/expSumPmf); + for (PointState &pointState : points_) + { + if (pointState.inTargetRegion()) + { + pointState.setLogPmfSum(pointState.logPmfSum() + logRenorm); + } + } +} + +void BiasState::initGridPointState(const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + const Grid &grid, + const BiasParams ¶ms, + const std::string &filename, + int numBias) +{ + /* Modify PMF, free energy and the constant target distribution factor + * to user input values if there is data given. + */ + if (awhBiasParams.bUserData) + { + readUserPmfAndTargetDistribution(dimParams, grid, filename, numBias, params.biasIndex, &points_); + setFreeEnergyToConvolvedPmf(dimParams, grid); + } + + /* The local Boltzmann distribution is special because the target distribution is updated as a function of the reference weighthistogram. */ + GMX_RELEASE_ASSERT(params.eTarget != eawhtargetLOCALBOLTZMANN || + points_[0].weightSumRef() != 0, + "AWH reference weight histogram not initialized properly with local Boltzmann target distribution."); + + updateTargetDistribution(points_, params); + + for (PointState &pointState : points_) + { + if (pointState.inTargetRegion()) + { + pointState.updateBias(); + } + else + { + /* Note that for zero target this is a value that represents -infinity but should not be used for biasing. */ + pointState.setTargetToZero(); + } + } + + /* Set the initial reference weighthistogram. */ + const double histogramSize = histogramSize_.histogramSize(); + for (auto &pointState : points_) + { + pointState.setInitialReferenceWeightHistogram(histogramSize); + } + + /* Make sure the pmf is normalized consistently with the histogram size. + Note: the target distribution and free energy need to be set here. */ + normalizePmf(params.numSharedUpdate); +} + +BiasState::BiasState(const AwhBiasParams &awhBiasParams, + double histogramSizeInitial, + const std::vector &dimParams, + const Grid &grid) : + coordState_(awhBiasParams, dimParams, grid), + points_(grid.numPoints()), + weightSumCovering_(grid.numPoints()), + histogramSize_(awhBiasParams, histogramSizeInitial) +{ + /* The minimum and maximum multidimensional point indices that are affected by the next update */ + for (size_t d = 0; d < dimParams.size(); d++) + { + int index = grid.point(coordState_.gridpointIndex()).index[d]; + originUpdatelist_[d] = index; + endUpdatelist_[d] = index; + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/biasstate.h b/src/gromacs/awh/biasstate.h new file mode 100644 index 0000000000..27ceb0d3de --- /dev/null +++ b/src/gromacs/awh/biasstate.h @@ -0,0 +1,533 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the BiasState class. + * + * The data members of this class are the state variables of the bias. + * All interaction from the outside happens through the Bias class, which + * holds important helper classes such as DimParams and Grid. + * This class holds many methods, but more are const methods that compute + * properties of the state. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_BIASSTATE_H +#define GMX_AWH_BIASSTATE_H + +#include + +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" +#include "gromacs/utility/gmxassert.h" + +#include "coordstate.h" +#include "dimparams.h" +#include "histogramsize.h" + +struct gmx_multisim_t; +struct t_commrec; + +namespace gmx +{ + +struct AwhBiasHistory; +struct AwhBiasParams; +class BiasParams; +class Grid; +class GridAxis; +class PointState; + +/*! \internal + * \brief The state of a bias. + * + * The bias state has the current coordinate state: its value and the grid point + * it maps to (the grid point of the umbrella potential if needed). It contains + * a vector with the state for each point on the grid. It also + * counts the number of updates issued and tracks which points have been sampled + * since last update. Finally, the convergence state is a global property set + * ultimately by the histogram size histogramSize in the sub-class HistogramSize, + * since the update sizes are ~ 1/histogramSize. + */ +class BiasState +{ + public: + /*! \brief Constructor. + * + * Constructs the global state and the point states on a provided + * geometric grid passed in \p grid. + * + * \param[in] awhBiasParams The Bias parameters from inputrec. + * \param[in] histogramSizeInitial The estimated initial histogram size. + * This is floating-point, since histograms use weighted + * entries and grow by a floating-point scaling factor. + * \param[in] dimParams The dimension parameters. + * \param[in] grid The bias grid. + */ + BiasState(const AwhBiasParams &awhBiasParams, + double histogramSizeInitial, + const std::vector &dimParams, + const Grid &grid); + + /*! \brief + * Restore the bias state from history. + * + * \param[in] biasHistory Bias history struct. + * \param[in] grid The bias grid. + */ + void restoreFromHistory(const AwhBiasHistory &biasHistory, + const Grid &grid); + + /*! \brief + * Broadcast the bias state over the MPI ranks in this simulation. + * + * \param[in] commRecord Struct for communication. + */ + void broadcast(const t_commrec *commRecord); + + /*! \brief + * Allocate and initialize a bias history with the given bias state. + * + * This function will be called at the start of a new simulation. + * Note that this only sets the correct size and does produces + * a history object with all variables zero. + * History data is set by \ref updateHistory. + * + * \param[in,out] biasHistory AWH history to initialize. + */ + void initHistoryFromState(AwhBiasHistory *biasHistory) const; + + /*! \brief + * Update the bias history with a new state. + * + * \param[out] biasHistory Bias history struct. + * \param[in] grid The bias grid. + */ + void updateHistory(AwhBiasHistory *biasHistory, + const Grid &grid) const; + + private: + /*! \brief Convolves the given PMF using the given AWH bias. + * + * \note: The PMF is in single precision, because it is a statistical + * quantity and therefore never reaches full float precision. + * + * \param[in] dimParams The bias dimensions parameters + * \param[in] grid The grid. + * \param[in,out] convolvedPmf Array returned will be of the same length as the AWH grid to store the convolved PMF in. + */ + void calcConvolvedPmf(const std::vector &dimParams, + const Grid &grid, + std::vector *convolvedPmf) const; + + /*! \brief + * Convolves the PMF and sets the initial free energy to its convolution. + * + * \param[in] dimParams The bias dimensions parameters + * \param[in] grid The bias grid. + */ + void setFreeEnergyToConvolvedPmf(const std::vector &dimParams, + const Grid &grid); + + /*! \brief + * Normalize the PMF histogram. + * + * \param[in] numSharingSims The number of simulations sharing the bias. + */ + void normalizePmf(int numSharingSims); + + public: + /*! \brief + * Initialize the state of grid coordinate points. + * + * \param[in] awhBiasParams Bias parameters from inputrec. + * \param[in] dimParams The dimension parameters. + * \param[in] grid The grid. + * \param[in] params The bias parameters. + * \param[in] filename Name of file to read PMF and target from. + * \param[in] numBias The number of biases. + */ + void initGridPointState(const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + const Grid &grid, + const BiasParams ¶ms, + const std::string &filename, + int numBias); + + /*! \brief + * Performs statistical checks on the collected histograms and warns if issues are detected. + * + * \param[in] grid The grid. + * \param[in] biasIndex The index of the bias we are checking for. + * \param[in] t Time. + * \param[in,out] fplog Output file for warnings. + * \param[in] maxNumWarnings Don't issue more than this number of warnings. + * \returns the number of warnings issued. + */ + int warnForHistogramAnomalies(const Grid &grid, + int biasIndex, + double t, + FILE *fplog, + int maxNumWarnings) const; + + /*! \brief + * Calculates and sets the force the coordinate experiences from an umbrella centered at the given point. + * + * The umbrella potential is an harmonic potential given by 0.5k(coord value - point value)^2. This + * value is also returned. + * + * \param[in] dimParams The bias dimensions parameters. + * \param[in] grid The grid. + * \param[in] point Point for umbrella center. + * \param[in,out] force Force vector to set. + * Returns the umbrella potential. + */ + double calcUmbrellaForceAndPotential(const std::vector &dimParams, + const Grid &grid, + int point, + awh_dvec force) const; + + /*! \brief + * Calculates and sets the convolved force acting on the coordinate. + * + * The convolved force is the weighted sum of forces from umbrellas + * located at each point in the grid. + * + * \param[in] dimParams The bias dimensions parameters. + * \param[in] grid The grid. + * \param[in] probWeightNeighbor Probability weights of the neighbors. + * \param[in,out] force Bias force vector to set. + */ + void calcConvolvedForce(const std::vector &dimParams, + const Grid &grid, + const std::vector &probWeightNeighbor, + awh_dvec force) const; + + /*! \brief + * Move the center point of the umbrella potential. + * + * A new umbrella center is sampled from the biased distibution. Also, the bias + * force is updated and the new potential is return. + * + * This function should only be called when the bias force is not being convolved. + * It is assumed that the probability distribution has been updated. + * + * \param[in] dimParams Bias dimension parameters. + * \param[in] grid The grid. + * \param[in] probWeightNeighbor Probability weights of the neighbors. + * \param[in,out] biasForce The AWH bias force. + * \param[in] step Step number, needed for the random number generator. + * \param[in] seed Random seed. + * \param[in] indexSeed Second random seed, should be the bias Index. + * \returns the new potential value. + */ + double moveUmbrella(const std::vector &dimParams, + const Grid &grid, + const std::vector &probWeightNeighbor, + awh_dvec biasForce, + gmx_int64_t step, + gmx_int64_t seed, + int indexSeed); + + private: + /*! \brief + * Gets the histogram rescaling factors needed for skipped updates. + * + * \param[in] params The bias parameters. + * \param[out] weighthistScaling Scaling factor for the reference weight histogram. + * \param[out] logPmfsumScaling Log of the scaling factor for the PMF histogram. + */ + void getSkippedUpdateHistogramScaleFactors(const BiasParams ¶ms, + double *weighthistScaling, + double *logPmfsumScaling) const; + + public: + /*! \brief + * Do all previously skipped updates. + * Public for use by tests. + * + * \param[in] params The bias parameters. + */ + void doSkippedUpdatesForAllPoints(const BiasParams ¶ms); + + /*! \brief + * Do previously skipped updates in this neighborhood. + * + * \param[in] params The bias parameters. + * \param[in] grid The grid. + */ + void doSkippedUpdatesInNeighborhood(const BiasParams ¶ms, + const Grid &grid); + + private: + /*! \brief + * Reset the range used to make the local update list. + * + * \param[in] grid The grid. + */ + void resetLocalUpdateRange(const Grid &grid); + + /*! \brief + * Returns the new size of the reference weight histogram in the initial stage. + * + * This function also takes care resetting the histogram used for covering checks + * and for exiting the initial stage. + * + * \param[in] params The bias parameters. + * \param[in] t Time. + * \param[in] detectedCovering True if we detected that the sampling interval has been sufficiently covered. + * \param[in,out] fplog Log file. + * \returns the new histogram size. + */ + double newHistogramSizeInitialStage(const BiasParams ¶ms, + double t, + bool detectedCovering, + FILE *fplog); + + /*! \brief + * Check if the sampling region has been covered "enough" or not. + * + * A one-dimensional interval is defined as covered if each point has + * accumulated the same weight as is in the peak of a discretized normal + * distribution. For multiple dimensions, the weights are simply projected + * onto each dimension and the multidimensional space is covered if each + * dimension is. + * + * \note The covering criterion for multiple dimensions could improved, e.g. + * by using a path finding algorithm. + * + * \param[in] params The bias parameters. + * \param[in] dimParams Bias dimension parameters. + * \param[in] grid The grid. + * \param[in] multiSimComm Struct for multi-simulation communication. + * \returns true if covered. + */ + bool isSamplingRegionCovered(const BiasParams ¶ms, + const std::vector &dimParams, + const Grid &grid, + const gmx_multisim_t *multiSimComm) const; + + /*! \brief + * Return the new reference weight histogram size for the current update. + * + * This function also takes care of checking for covering in the initial stage. + * + * \param[in] params The bias parameters. + * \param[in] t Time. + * \param[in] covered True if the sampling interval has been covered enough. + * \param[in,out] fplog Log file. + * \returns the new histogram size. + */ + double newHistogramSize(const BiasParams ¶ms, + double t, + bool covered, + FILE *fplog); + + public: + /*! \brief + * Update the reaction coordinate value. + * + * \param[in] grid The bias grid. + * \param[in] coordValue The current reaction coordinate value (there are no limits on allowed values). + */ + void setCoordValue(const Grid &grid, + const awh_dvec coordValue) + { + coordState_.setCoordValue(grid, coordValue); + }; + + /*! \brief + * Performs an update of the bias. + * + * The objective of the update is to use collected samples (probability weights) + * to improve the free energy estimate. For sake of efficiency, the update is + * local whenever possible, meaning that only points that have actually been sampled + * are accessed and updated here. For certain AWH settings or at certain steps + * however, global need to be performed. Besides the actual free energy update, this + * function takes care of ensuring future convergence of the free energy. Convergence + * is obtained by increasing the size of the reference weight histogram in a controlled + * (sometimes dynamic) manner. Also, there are AWH variables that are direct functions + * of the free energy or sampling history that need to be updated here, namely the target + * distribution and the bias function. + * + * \param[in] dimParams The dimension parameters. + * \param[in] grid The grid. + * \param[in] params The bias parameters. + * \param[in] ms Struct for multi-simulation communication. + * \param[in] t Time. + * \param[in] step Time step. + * \param[in,out] fplog Log file. + * \param[in,out] updateList Work space to store a temporary list. + */ + void updateFreeEnergyAndAddSamplesToHistogram(const std::vector &dimParams, + const Grid &grid, + const BiasParams ¶ms, + const gmx_multisim_t *ms, + double t, + gmx_int64_t step, + FILE *fplog, + std::vector *updateList); + + /*! \brief + * Update the probability weights and the convolved bias. + * + * Given a coordinate value, each grid point is assigned a probability + * weight, w(point|value), that depends on the current bias function. The sum + * of these weights is needed for normalizing the probability sum to 1 but + * also equals the effective, or convolved, biasing weight for this coordinate + * value. The convolved bias is needed e.g. for extracting the PMF, so we save + * it here since this saves us from doing extra exponential function evaluations + * later on. + * + * \param[in] dimParams The bias dimensions parameters + * \param[in] grid The grid. + * \param[out] weight Probability weights of the neighbors. + * \returns the convolved bias. + */ + + double updateProbabilityWeightsAndConvolvedBias(const std::vector &dimParams, + const Grid &grid, + std::vector *weight) const; + + /*! \brief + * Take samples of the current probability weights for future updates and analysis. + * + * Points in the current neighborhood will now have data meaning they + * need to be included in the local update list of the next update. + * Therefore, the local update range is also update here. + * + * \param[in] grid The grid. + * \param[in] probWeightNeighbor Probability weights of the neighbors. + */ + void sampleProbabilityWeights(const Grid &grid, + const std::vector &probWeightNeighbor); + + /*! \brief + * Sample the reaction coordinate and PMF for future updates or analysis. + * + * These samples do not affect the (future) sampling and are thus + * pure observables. Statisics of these are stored in the energy file. + * + * \param[in] grid The grid. + * \param[in] probWeightNeighbor Probability weights of the neighbors. + * \param[in] convolvedBias The convolved bias. + */ + void sampleCoordAndPmf(const Grid &grid, + const std::vector &probWeightNeighbor, + double convolvedBias); + /*! \brief + * Calculates the convolved bias for a given coordinate value. + * + * The convolved bias is the effective bias acting on the coordinate. + * Since the bias here has arbitrary normalization, this only makes + * sense as a relative, to other coordinate values, measure of the bias. + * + * \note If it turns out to be costly to calculate this pointwise + * the convolved bias for the whole grid could be returned instead. + * + * \param[in] dimParams The bias dimensions parameters + * \param[in] grid The grid. + * \param[in] coordValue Coordinate value. + * \returns the convolved bias >= -GMX_DOUBLE_MAX. + */ + double calcConvolvedBias(const std::vector &dimParams, + const Grid &grid, + const awh_dvec &coordValue) const; + + /*! \brief + * Fills the given array with PMF values, resizes if necessary. + * + * Points outside of the biasing target region will get PMF = GMX_FLOAT_MAX. + * \note: The PMF is in single precision, because it is a statistical + * quantity and therefore never reaches full float precision. + * + * \param[in,out] pmf Array returned will be of the same length as the AWH grid to store the PMF in. + */ + void getPmf(std::vector *pmf) const; + + /*! \brief Returns the current coordinate state. + */ + const CoordState &coordState() const + { + return coordState_; + }; + + /*! \brief Returns a const reference to the point state. + */ + const std::vector &points() const + { + return points_; + }; + + /*! \brief Returns true if we are in the initial stage. + */ + bool inInitialStage() const + { + return histogramSize_.inInitialStage(); + }; + + /* Data members */ + private: + CoordState coordState_; /**< The Current coordinate state */ + + /* The grid point state */ + std::vector points_; /**< Vector of state of the grid points */ + + /* Covering values for each point on the grid */ + std::vector weightSumCovering_; /**< Accumulated weights for covering checks */ + + HistogramSize histogramSize_; /**< Global histogram size related values. */ + + /* Track the part of the grid sampled since the last update. */ + awh_ivec originUpdatelist_; /**< The origin of the rectangular region that has been sampled since last update. */ + awh_ivec endUpdatelist_; /**< The end of the rectangular region that has been sampled since last update. */ +}; + +//! Linewidth used for warning output +static const int c_linewidth = 80 - 2; + +//! Indent used for warning output +static const int c_indent = 0; + +} // namespace gmx + +#endif /* GMX_AWH_BIASSTATE_H */ diff --git a/src/gromacs/awh/coordstate.cpp b/src/gromacs/awh/coordstate.cpp new file mode 100644 index 0000000000..108b50c596 --- /dev/null +++ b/src/gromacs/awh/coordstate.cpp @@ -0,0 +1,170 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the CoordState class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "coordstate.h" + +#include + +#include "gromacs/math/utilities.h" +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/random/threefry.h" +#include "gromacs/random/uniformrealdistribution.h" +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/gmxassert.h" + +#include "grid.h" + +namespace gmx +{ + +CoordState::CoordState(const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + const Grid &grid) +{ + for (size_t d = 0; d < dimParams.size(); d++) + { + coordValue_[d] = dimParams[d].scaleUserInputToInternal(awhBiasParams.dimParams[d].coordValueInit); + } + + /* The grid-point index is always the nearest point to the coordinate. + * We initialize the umbrella location to the same nearest point. + * More correctly one would sample from the biased distribution, + * but it doesn't really matter, as this will happen after a few steps. + */ + gridpointIndex_ = grid.nearestIndex(coordValue_); + umbrellaGridpoint_ = gridpointIndex_; +} + +namespace +{ + +/*! \brief Generate a sample from a discrete probability distribution defined on [0, distr.size() - 1]. + * + * The pair (indexSeed0,indexSeed1) should be different for every invocation. + * + * \param[in] distr Normalized probability distribution to generate a sample from. + * \param[in] seed Random seed for initializing the random number generator. + * \param[in] indexSeed0 Random seed needed by the random number generator. + * \param[in] indexSeed1 Random seed needed by the random number generator. + * \returns a sample index in [0, distr.size() - 1] + */ +int getSampleFromDistribution(ArrayRef distr, + gmx_int64_t seed, + gmx_int64_t indexSeed0, + gmx_int64_t indexSeed1) +{ + gmx::ThreeFry2x64<0> rng(seed, gmx::RandomDomain::AwhBiasing); + gmx::UniformRealDistribution uniformRealDistr; + + GMX_RELEASE_ASSERT(distr.size() > 0, "We need a non-zero length distribution to sample from"); + + /* Generate the cumulative probability distribution function */ + std::vector cumulativeDistribution(distr.size()); + + cumulativeDistribution[0] = distr[0]; + + for (size_t i = 1; i < distr.size(); i++) + { + cumulativeDistribution[i] = cumulativeDistribution[i - 1] + distr[i]; + } + + GMX_RELEASE_ASSERT(gmx_within_tol(cumulativeDistribution.back(), 1.0, 0.01), "Attempt to get sample from non-normalized/zero distribution"); + + /* Use binary search to convert the real value to an integer in [0, ndistr - 1] distributed according to distr. */ + rng.restart(indexSeed0, indexSeed1); + + double value = uniformRealDistr(rng); + int sample = std::upper_bound(cumulativeDistribution.begin(), cumulativeDistribution.end() - 1, value) - cumulativeDistribution.begin(); + + return sample; +} + +} // namespace + +void +CoordState::sampleUmbrellaGridpoint(const Grid &grid, + int gridpointIndex, + const std::vector &probWeightNeighbor, + gmx_int64_t step, + gmx_int64_t seed, + int indexSeed) +{ + /* Sample new umbrella reference value from the probability distribution + * which is defined for the neighboring points of the current coordinate. + */ + const std::vector &neighbor = grid.point(gridpointIndex).neighbor; + + /* In order to use the same seed for all AWH biases and get independent + samples we use the index of the bias. */ + int localIndex = getSampleFromDistribution(probWeightNeighbor, + seed, step, indexSeed); + + umbrellaGridpoint_ = neighbor[localIndex]; +} + +void CoordState::setCoordValue(const Grid &grid, + const awh_dvec coordValue) +{ + for (int dim = 0; dim < grid.numDimensions(); dim++) + { + coordValue_[dim] = coordValue[dim]; + } + + /* The grid point closest to the coordinate value defines the current + * neighborhood of points. Besides at steps when global updates and/or + * checks are performed, only the neighborhood will be touched. + */ + gridpointIndex_ = grid.nearestIndex(coordValue_); +} + +void +CoordState::restoreFromHistory(const AwhBiasStateHistory &stateHistory) +{ + umbrellaGridpoint_ = stateHistory.umbrellaGridpoint; +} + +} // namespace gmx diff --git a/src/gromacs/awh/coordstate.h b/src/gromacs/awh/coordstate.h new file mode 100644 index 0000000000..b6a9c8f07e --- /dev/null +++ b/src/gromacs/awh/coordstate.h @@ -0,0 +1,145 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the CoordState class. + * + * It sets and holds the current coordinate value and corresponding closest + * grid point index. These are (re)set at every step. + * With umbrella potential type, this class also holds and updates the umbrella + * potential reference location, which is a state variable that presists over + * the duration of an AWH sampling interval. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_COORDSTATE_H +#define GMX_AWH_COORDSTATE_H + +#include + +#include "dimparams.h" + +namespace gmx +{ + +struct AwhBiasParams; +struct AwhBiasStateHistory; +class BiasParams; +class Grid; + +/*! \internal \brief Keeps track of the current coordinate value, grid index and umbrella location. + */ +class CoordState +{ + public: + /*! \brief Constructor. + * + * \param[in] awhBiasParams The Bias parameters from inputrec. + * \param[in] dimParams The dimension Parameters. + * \param[in] grid The grid. + */ + CoordState(const AwhBiasParams &awhBiasParams, + const std::vector &dimParams, + const Grid &grid); + + /*! \brief + * Sample a new umbrella reference point given the current coordinate value. + * + * It is assumed that the probability distribution has been updated. + * + * \param[in] grid The grid. + * \param[in] gridpointIndex The grid point, sets the neighborhood. + * \param[in] probWeightNeighbor Probability weights of the neighbors. + * \param[in] step Step number, needed for the random number generator. + * \param[in] seed Random seed. + * \param[in] indexSeed Second random seed, should be the bias Index. + * \returns the index of the sampled point. + */ + void sampleUmbrellaGridpoint(const Grid &grid, + int gridpointIndex, + const std::vector &probWeightNeighbor, + gmx_int64_t step, + gmx_int64_t seed, + int indexSeed); + + /*! \brief Update the coordinate value with coordValue. + * + * \param[in] grid The grid. + * \param[in] coordValue The new coordinate value. + */ + void setCoordValue(const Grid &grid, + const awh_dvec coordValue); + + /*! \brief Restores the coordinate state from history. + * + * \param[in] stateHistory The AWH bias state history. + */ + void restoreFromHistory(const AwhBiasStateHistory &stateHistory); + + /*! \brief Returns the current coordinate value. + */ + const awh_dvec &coordValue() const + { + return coordValue_; + }; + + /*! \brief Returns the grid point index for the current coordinate value. + */ + int gridpointIndex() const + { + return gridpointIndex_; + } + + /*! \brief Returns the index for the current reference grid point. + */ + int umbrellaGridpoint() const + { + return umbrellaGridpoint_; + }; + + private: + awh_dvec coordValue_; /**< Current coordinate value in (nm or rad) */ + int gridpointIndex_; /**< The grid point index for the current coordinate value */ + int umbrellaGridpoint_; /**< Index for the current reference grid point for the umbrella, only used with umbrella potential type */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_COORDSTATE_H */ diff --git a/src/gromacs/awh/dimparams.h b/src/gromacs/awh/dimparams.h new file mode 100644 index 0000000000..8abf5aa5c9 --- /dev/null +++ b/src/gromacs/awh/dimparams.h @@ -0,0 +1,116 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the DimParams struct and AWH vector types. + * + * This class holds the physical information for a dimension + * of the bias reaction-coordinate grid. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_DIMPARAMS_H +#define GMX_AWH_DIMPARAMS_H + +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" + +namespace gmx +{ + +//! The maximum dimensionality of the AWH coordinate. +static const int c_biasMaxNumDim = 4; + +//! A real vector in AWH coordinate space. +typedef double awh_dvec[c_biasMaxNumDim]; + +//! An integer vector in AWH coordinate space. +typedef int awh_ivec[c_biasMaxNumDim]; + +/*! \internal \brief Constant parameters for each dimension of the coordinate. + */ +struct DimParams +{ + /*! \brief + * Constructor. + * + * \param[in] conversionFactor Conversion factor from user coordinate units to bias internal units (=DEG2RAD for angles). + * \param[in] forceConstant The harmonic force constant. + * \param[in] beta 1/(k_B T). + */ + DimParams(double conversionFactor, + double forceConstant, + double beta) : + k(forceConstant), + betak(beta*forceConstant), + userCoordUnitsToInternal(conversionFactor) + { + }; + + /*! \brief Convert internal coordinate units to external, user coordinate units. + * + * \param[in] value Value to convert. + * \returns the converted value. + */ + double scaleInternalToUserInput(double value) const + { + return value/userCoordUnitsToInternal; + } + + /*! \brief Convert external, user coordinate units to internal coordinate units. + * + * \param[in] value Value to convert. + * \returns the converted value. + */ + double scaleUserInputToInternal(double value) const + { + return value*userCoordUnitsToInternal; + } + + const double k; /**< Force constant (kJ/mol/nm^2) for each coordinate dimension. */ + const double betak; /**< Inverse variance (1/nm^2) for each coordinate dimension. */ + const double userCoordUnitsToInternal; /**< Conversion factor coordinate units. */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_DIMPARAMS_H */ diff --git a/src/gromacs/awh/grid.cpp b/src/gromacs/awh/grid.cpp new file mode 100644 index 0000000000..26c6cc3e43 --- /dev/null +++ b/src/gromacs/awh/grid.cpp @@ -0,0 +1,835 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements functions in grid.h. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "grid.h" + +#include + +#include +#include + +#include + +#include "gromacs/math/utilities.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/cstringutil.h" +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/smalloc.h" +#include "gromacs/utility/stringutil.h" + +namespace gmx +{ + +namespace +{ + +/*! \brief + * Modify x so that it is periodic in [-period/2, +period/2). + * + * x is modified by shifting its value by a +/- a period if + * needed. Thus, it is assumed that x is at most one period + * away from this interval. For period = 0, x is not modified. + * + * \param[in,out] x Pointer to the value to modify. + * \param[in] period The period, or 0 if not periodic. + */ +void centerPeriodicValueAroundZero(double *x, + double period) +{ + GMX_ASSERT(period >= 0, "Periodic should not be negative"); + + const double halfPeriod = period*0.5; + + if (*x >= halfPeriod) + { + *x -= period; + } + else if (*x < -halfPeriod) + { + *x += period; + } +} + +/*! \brief + * If period>0, retrun x so that it is periodic in [0, period), else return x. + * + * Return x is shifted its value by a +/- a period, if + * needed. Thus, it is assumed that x is at most one period + * away from this interval. For this domain and period > 0 + * this is equivalent to x = x % period. For period = 0, + * x is not modified. + * + * \param[in,out] x Pointer to the value to modify, should be >= 0. + * \param[in] period The period, or 0 if not periodic. + * \returns for period>0: index value witin [0, period), otherwise: \p x. + */ +int indexWithinPeriod(int x, + int period) +{ + GMX_ASSERT(period >= 0, "Periodic should not be negative"); + + if (period == 0) + { + return x; + } + + GMX_ASSERT(x > -period && x < 2*period, "x should not be more shifted by more than one period"); + + if (x >= period) + { + return x - period; + } + else if (x < 0) + { + return x + period; + } + else + { + return x; + } +} + +/*! \brief + * Get the length of the interval (origin, end). + * + * This returns the distance obtained by connecting the origin point to + * the end point in the positive direction. Note that this is generally + * not the shortest distance. For period > 0, both origin and + * end are expected to take values in the same periodic interval, + * ie. |origin - end| < period. + * + * \param[in] origin Start value of the interval. + * \param[in] end End value of the interval. + * \param[in] period The period, or 0 if not periodic. + * \returns the interval length from origin to end. + */ +double getIntervalLengthPeriodic(double origin, + double end, + double period) +{ + double length = end - origin; + if (length < 0) + { + /* The interval wraps around the +/- boundary which has a discontinuous jump of -period. */ + length += period; + } + + GMX_RELEASE_ASSERT(length >= 0, "Negative AWH grid axis length."); + GMX_RELEASE_ASSERT(period == 0 || length <= period, "Interval length longer than period."); + + return length; +} + +/*! \brief + * Get the deviation x - x0. + * + * For period > 0, the deviation with minimum absolute value is returned, + * i.e. with a value in the interval [-period/2, +period/2). + * Also for period > 0, it is assumed that |x - x0| < period. + * + * \param[in] x From value. + * \param[in] x0 To value. + * \param[in] period The period, or 0 if not periodic. + * \returns the deviation from x to x0. + */ +double getDeviationPeriodic(double x, + double x0, + double period) +{ + double dev = x - x0; + + if (period > 0) + { + centerPeriodicValueAroundZero(&dev, period); + } + + return dev; +} + +} // namespace + +double getDeviationFromPointAlongGridAxis(const Grid &grid, + int dimIndex, + int pointIndex, + double value) +{ + double coordValue = grid.point(pointIndex).coordValue[dimIndex]; + + return getDeviationPeriodic(value, coordValue, grid.axis(dimIndex).period()); +} + +void linearArrayIndexToMultiDim(int indexLinear, int numDimensions, const awh_ivec numPointsDim, awh_ivec indexMulti) +{ + for (int d = 0; d < numDimensions; d++) + { + int stride = 1; + + /* Workaround for bug in clang */ +#ifndef __clang_analyzer__ + for (int k = d + 1; k < numDimensions; k++) + { + stride *= numPointsDim[k]; + } +#endif + + indexMulti[d] = indexLinear/stride; + indexLinear -= indexMulti[d]*stride; + } +} + +void linearGridindexToMultiDim(const Grid &grid, + int indexLinear, + awh_ivec indexMulti) +{ + awh_ivec numPointsDim; + + for (int d = 0; d < grid.numDimensions(); d++) + { + numPointsDim[d] = grid.axis(d).numPoints(); + } + + linearArrayIndexToMultiDim(indexLinear, grid.numDimensions(), numPointsDim, indexMulti); +} + + +int multiDimArrayIndexToLinear(const awh_ivec indexMulti, + int numDimensions, + const awh_ivec numPointsDim) +{ + int stride = 1; + int indexLinear = 0; + for (int d = numDimensions - 1; d >= 0; d--) + { + indexLinear += stride*indexMulti[d]; + stride *= numPointsDim[d]; + } + + return indexLinear; +} + +namespace +{ + +/*! \brief Convert a multidimensional grid point index to a linear one. + * + * \param[in] axis The grid axes. + * \param[in] indexMulti Multidimensional grid point index to convert to a linear one. + * \returns the linear index. + */ +int multiDimGridIndexToLinear(const std::vector &axis, + const awh_ivec indexMulti) +{ + awh_ivec numPointsDim = { 0 }; + + for (size_t d = 0; d < axis.size(); d++) + { + numPointsDim[d] = axis[d].numPoints(); + } + + return multiDimArrayIndexToLinear(indexMulti, axis.size(), numPointsDim); +} + +} // namespace + +int multiDimGridIndexToLinear(const Grid &grid, + const awh_ivec indexMulti) +{ + return multiDimGridIndexToLinear(grid.axis(), indexMulti); +} + +namespace +{ + +/*! \brief + * Take a step in a multidimensional array. + * + * The multidimensional index gives the starting point to step from. Dimensions are + * stepped through in order of decreasing dimensional index such that the index is + * incremented in the highest dimension possible. If the starting point is the end + * of the array, a step cannot be taken and the index is not modified. + * + * \param[in] numDim Number of dimensions of the array. + * \param[in] numPoints Vector with the number of points along each dimension. + * \param[in,out] indexDim Multidimensional index, each with values in [0, numPoints[d] - 1]. + * \returns true if a step was taken, false if not. + */ +bool stepInMultiDimArray(int numDim, + const awh_ivec numPoints, + awh_ivec indexDim) +{ + bool haveStepped = false; + + for (int d = numDim - 1; d >= 0 && !haveStepped; d--) + { + if (indexDim[d] < numPoints[d] - 1) + { + /* Not at a boundary, just increase by 1. */ + indexDim[d]++; + haveStepped = true; + } + else + { + /* At a boundary. If we are not at the end of the array, + reset the index and check if we can step in higher dimensions */ + if (d > 0) + { + indexDim[d] = 0; + } + } + } + + return haveStepped; +} + +/*! \brief + * Transforms a grid point index to to the multidimensional index of a subgrid. + * + * The subgrid is defined by the location of its origin and the number of points + * along each dimension. The index transformation thus consists of a projection + * of the linear index onto each dimension, followed by a translation of the origin. + * The subgrid may have parts that don't overlap with the grid. E.g. the origin + * vector can have negative components meaning the origin lies outside of the grid. + * However, the given point needs to be both a grid and subgrid point. + * + * Periodic boundaries are taken care of by wrapping the subgrid around the grid. + * Thus, for periodic dimensions the number of subgrid points need to be less than + * the number of points in a period to prevent problems of wrapping around. + * + * \param[in] grid The grid. + * \param[in] subgridOrigin Vector locating the subgrid origin relative to the grid origin. + * \param[in] subgridNpoints The number of subgrid points in each dimension. + * \param[in] point Grid point to get subgrid index for. + * \param[in,out] subgridIndex Subgrid multidimensional index. + */ +void gridToSubgridIndex(const Grid &grid, + const awh_ivec subgridOrigin, + const awh_ivec subgridNpoints, + int point, + awh_ivec subgridIndex) +{ + /* Get the subgrid index of the given grid point, for each dimension. */ + for (int d = 0; d < grid.numDimensions(); d++) + { + /* The multidimensional grid point index relative to the subgrid origin. */ + subgridIndex[d] = + indexWithinPeriod(grid.point(point).index[d] - subgridOrigin[d], + grid.axis(d).numPointsInPeriod()); + + /* The given point should be in the subgrid. */ + GMX_RELEASE_ASSERT((subgridIndex[d] >= 0) && (subgridIndex[d] < subgridNpoints[d]), + "Attempted to convert an AWH grid point index not in subgrid to out of bounds subgrid index"); + } +} + +/*! \brief + * Transform a multidimensional subgrid index to a grid point index. + * + * If the given subgrid point is not a grid point the transformation will not be successful + * and the grid point index will not be set. Periodic boundaries are taken care of by + * wrapping the subgrid around the grid. + * + * \param[in] grid The grid. + * \param[in] subgridOrigin Vector locating the subgrid origin relative to the grid origin. + * \param[in] subgridIndex Subgrid multidimensional index to get grid point index for. + * \param[in,out] gridIndex Grid point index. + * \returns true if the transformation was successful. + */ +bool subgridToGridIndex(const Grid &grid, + const awh_ivec subgridOrigin, + const awh_ivec subgridIndex, + int *gridIndex) +{ + awh_ivec globalIndexDim; + + /* Check and apply boundary conditions for each dimension */ + for (int d = 0; d < grid.numDimensions(); d++) + { + /* Transform to global multidimensional indexing by adding the origin */ + globalIndexDim[d] = subgridOrigin[d] + subgridIndex[d]; + + /* The local grid is allowed to stick out on the edges of the global grid. Here the boundary conditions are applied.*/ + if (globalIndexDim[d] < 0 || globalIndexDim[d] > grid.axis(d).numPoints() - 1) + { + /* Try to wrap around if periodic. Otherwise, the transformation failed so return. */ + if (!grid.axis(d).isPeriodic()) + { + return false; + } + + /* The grid might not contain a whole period. Can only wrap around if this gap is not too large. */ + int gap = grid.axis(d).numPointsInPeriod() - grid.axis(d).numPoints(); + + int bridge; + int numWrapped; + if (globalIndexDim[d] < 0) + { + bridge = -globalIndexDim[d]; + numWrapped = bridge - gap; + if (numWrapped > 0) + { + globalIndexDim[d] = grid.axis(d).numPoints() - numWrapped; + } + } + else + { + bridge = globalIndexDim[d] - (grid.axis(d).numPoints() - 1); + numWrapped = bridge - gap; + if (numWrapped > 0) + { + globalIndexDim[d] = numWrapped - 1; + } + } + + if (numWrapped <= 0) + { + return false; + } + } + } + + /* Translate from multidimensional to linear indexing and set the return value */ + (*gridIndex) = multiDimGridIndexToLinear(grid, globalIndexDim); + + return true; +} + +} // namespace + +bool advancePointInSubgrid(const Grid &grid, + const awh_ivec subgridOrigin, + const awh_ivec subgridNumPoints, + int *gridPointIndex) +{ + /* Initialize the subgrid index to the subgrid origin. */ + awh_ivec subgridIndex = { 0 }; + + /* Get the subgrid index of the given grid point index. */ + if (*gridPointIndex >= 0) + { + gridToSubgridIndex(grid, subgridOrigin, subgridNumPoints, *gridPointIndex, subgridIndex); + } + else + { + /* If no grid point is given we start at the subgrid origin (which subgridIndex is initialized to). + If this is a valid grid point then we're done, otherwise keep looking below. */ + /* TODO: separate into a separate function (?) */ + if (subgridToGridIndex(grid, subgridOrigin, subgridIndex, gridPointIndex)) + { + return true; + } + } + + /* Traverse the subgrid and look for the first point that is also in the grid. */ + while (stepInMultiDimArray(grid.numDimensions(), subgridNumPoints, subgridIndex)) + { + /* If this is a valid grid point, the grid point index is updated.*/ + if (subgridToGridIndex(grid, subgridOrigin, subgridIndex, gridPointIndex)) + { + return true; + } + } + + return false; +} + +/*! \brief + * Returns the point distance between from value x to value x0 along the given axis. + * + * Note that the returned distance may be negative or larger than the + * number of points in the axis. For a periodic axis, the distance is chosen + * to be in [0, period), i.e. always positive but not the shortest one. + * + * \param[in] axis Grid axis. + * \param[in] x From value. + * \param[in] x0 To value. + * \returns (x - x0) in number of points. + */ +static int pointDistanceAlongAxis(const GridAxis &axis, double x, double x0) +{ + int distance = 0; + + if (axis.spacing() > 0) + { + /* Get the real-valued distance. For a periodic axis, the shortest one. */ + double period = axis.period(); + double dx = getDeviationPeriodic(x, x0, period); + + /* Transform the distance into a point distance. + Shift by +0.5 so we can use floor or integer casting below to get the integer index */ + distance = static_cast(floor(dx/axis.spacing() + 0.5)); + + /* If periodic, shift the point distance to be in [0, period) */ + distance = indexWithinPeriod(distance, axis.numPointsInPeriod()); + } + + return distance; +} + +/*! \brief + * Query if a value is in range of the grid. + * + * \param[in] value Value to check. + * \param[in] axis The grid axes. + * \returns true if the value is in the grid. + */ +static bool valueIsInGrid(const awh_dvec value, + const std::vector &axis) +{ + /* For each dimension get the one-dimensional index and check if it is in range. */ + for (size_t d = 0; d < axis.size(); d++) + { + /* The index is computed as the point distance from the origin. */ + int index = pointDistanceAlongAxis(axis[d], value[d], axis[d].origin()); + + if (!(index >= 0 && index < axis[d].numPoints())) + { + return false; + } + } + + return true; +} + +bool Grid::covers(const awh_dvec value) const +{ + return valueIsInGrid(value, axis()); +} + +int GridAxis::nearestIndex(double value) const +{ + /* Get the point distance to the origin. This may by an out of index range for the axis. */ + int index = pointDistanceAlongAxis(*this, value, origin_); + + if (index < 0 || index >= numPoints_) + { + if (isPeriodic()) + { + GMX_RELEASE_ASSERT(index >= 0 && index < numPointsInPeriod_, + "Index not in periodic interval 0 for AWH periodic axis"); + int endDistance = (index - (numPoints_ - 1)); + int originDistance = (numPointsInPeriod_ - index); + index = originDistance < endDistance ? 0 : numPoints_ - 1; + } + else + { + index = (index < 0) ? 0 : (numPoints_ - 1); + } + } + + return index; +} + +/*! \brief + * Map a value to the nearest point in the grid. + * + * \param[in] value Value. + * \param[in] axis The grid axes. + * \returns the point index nearest to the value. + */ +static int getNearestIndexInGrid(const awh_dvec value, + const std::vector &axis) +{ + awh_ivec indexMulti; + + /* If the index is out of range, modify it so that it is in range by choosing the nearest point on the edge. */ + for (size_t d = 0; d < axis.size(); d++) + { + indexMulti[d] = axis[d].nearestIndex(value[d]); + } + + return multiDimGridIndexToLinear(axis, indexMulti); +} + +int Grid::nearestIndex(const awh_dvec value) const +{ + return getNearestIndexInGrid(value, axis()); +} + +namespace +{ + +/*! \brief + * Find and set the neighbors of a grid point. + * + * The search space for neighbors is a subgrid with size set by a scope cutoff. + * In general not all point within scope will be valid grid points. + * + * \param[in] pointIndex Grid point index. + * \param[in] grid The grid. + * \param[in,out] neighborIndexArray Array to fill with neighbor indices. + */ +void setNeighborsOfGridPoint(int pointIndex, + const Grid &grid, + std::vector *neighborIndexArray) +{ + const int c_maxNeighborsAlongAxis = 1 + 2*static_cast(Grid::c_numPointsPerSigma*Grid::c_scopeCutoff); + + awh_ivec numCandidates = {0}; + awh_ivec subgridOrigin = {0}; + for (int d = 0; d < grid.numDimensions(); d++) + { + /* The number of candidate points along this dimension is given by the scope cutoff. */ + numCandidates[d] = std::min(c_maxNeighborsAlongAxis, + grid.axis(d).numPoints()); + + /* The origin of the subgrid to search */ + int centerIndex = grid.point(pointIndex).index[d]; + subgridOrigin[d] = centerIndex - numCandidates[d]/2; + } + + /* Find and set the neighbors */ + int neighborIndex = -1; + bool aPointExists = true; + + /* Keep looking for grid points while traversing the subgrid. */ + while (aPointExists) + { + /* The point index is updated if a grid point was found. */ + aPointExists = advancePointInSubgrid(grid, subgridOrigin, numCandidates, &neighborIndex); + + if (aPointExists) + { + neighborIndexArray->push_back(neighborIndex); + } + } +} + +} // namespace + +void Grid::initPoints() +{ + awh_ivec numPointsDimWork = { 0 }; + awh_ivec indexWork = { 0 }; + + for (size_t d = 0; d < axis_.size(); d++) + { + /* Temporarily gather the number of points in each dimension in one array */ + numPointsDimWork[d] = axis_[d].numPoints(); + } + + for (auto &point : point_) + { + for (size_t d = 0; d < axis_.size(); d++) + { + point.coordValue[d] = axis_[d].origin() + indexWork[d]*axis_[d].spacing(); + + if (axis_[d].period() > 0) + { + /* Do we always want the values to be centered around 0 ? */ + centerPeriodicValueAroundZero(&point.coordValue[d], axis_[d].period()); + } + + point.index[d] = indexWork[d]; + } + + stepInMultiDimArray(axis_.size(), numPointsDimWork, indexWork); + } +} + +GridAxis::GridAxis(double origin, double end, + double period, double pointDensity) : + origin_(origin), + period_(period) +{ + length_ = getIntervalLengthPeriodic(origin_, end, period_); + + /* Automatically determine number of points based on the user given endpoints + and the expected fluctuations in the umbrella. */ + if (length_ == 0) + { + numPoints_ = 1; + } + else if (pointDensity == 0) + { + numPoints_ = 2; + } + else + { + double lengthInPoints = length_*pointDensity; + + numPoints_ = 1 + static_cast(std::ceil(lengthInPoints)); + } + + /* Set point spacing based on the number of points */ + if (isPeriodic()) + { + /* Set the grid spacing so that a period is matched exactly by an integer number of points. + The number of points in a period is equal to the number of grid spacings in a period + since the endpoints are connected. */ + numPointsInPeriod_ = length_ > 0 ? static_cast(std::ceil(period/length_*(numPoints_ - 1))) : 1; + spacing_ = period_/numPointsInPeriod_; + + /* Modify the number of grid axis points to be compatible with the period dependent spacing. */ + numPoints_ = std::min(static_cast(round(length_/spacing_)) + 1, + numPointsInPeriod_); + } + else + { + numPointsInPeriod_ = 0; + spacing_ = numPoints_ > 1 ? length_/(numPoints_ - 1) : 0; + } +} + +GridAxis::GridAxis(double origin, double end, + double period, int numPoints) : + origin_(origin), + period_(period), + numPoints_(numPoints) +{ + length_ = getIntervalLengthPeriodic(origin_, end, period_); + spacing_ = numPoints_ > 1 ? length_/(numPoints_ - 1) : period_; + numPointsInPeriod_ = static_cast(std::round(period_/spacing_)); +} + +Grid::Grid(const std::vector &dimParams, + const AwhDimParams *awhDimParams) +{ + /* Define the discretization along each dimension */ + awh_dvec period; + int numPoints = 1; + for (size_t d = 0; d < dimParams.size(); d++) + { + double origin = dimParams[d].scaleUserInputToInternal(awhDimParams[d].origin); + double end = dimParams[d].scaleUserInputToInternal(awhDimParams[d].end); + period[d] = dimParams[d].scaleUserInputToInternal(awhDimParams[d].period); + static_assert(c_numPointsPerSigma >= 1.0, "The number of points per sigma should be at least 1.0 to get a uniformly covering the reaction using Gaussians"); + double pointDensity = std::sqrt(dimParams[d].betak)*c_numPointsPerSigma; + axis_.push_back(GridAxis(origin, end, period[d], pointDensity)); + numPoints *= axis_[d].numPoints(); + } + + point_.resize(numPoints); + + /* Set their values */ + initPoints(); + + /* Keep a neighbor list for each point. + * Note: could also generate neighbor list only when needed + * instead of storing them for each point. + */ + for (size_t m = 0; m < point_.size(); m++) + { + std::vector *neighbor = &point_[m].neighbor; + + setNeighborsOfGridPoint(m, *this, neighbor); + } +} + +void mapGridToDataGrid(std::vector *gridpointToDatapoint, + const double* const *data, + int numDataPoints, + const std::string &dataFilename, + const Grid &grid, + const std::string &correctFormatMessage) +{ + /* Transform the data into a grid in order to map each grid point to a data point + using the grid functions. */ + std::vector axis_; + + /* Count the number of points for each dimension. Each dimension + has its own stride. */ + int stride = 1; + int numPointsCounted = 0; + std::vector numPoints(grid.numDimensions()); + for (int d = grid.numDimensions() - 1; d >= 0; d--) + { + int numPointsInDim = 0; + int pointIndex = 0; + double firstValue = data[d][pointIndex]; + do + { + numPointsInDim++; + pointIndex += stride; + } + while (pointIndex < numDataPoints && + !gmx_within_tol(firstValue, data[d][pointIndex], GMX_REAL_EPS)); + + /* The stride in dimension dimension d - 1 equals the number of points + dimension d. */ + stride = numPointsInDim; + + numPointsCounted = (numPointsCounted == 0) ? numPointsInDim : numPointsCounted*numPointsInDim; + + numPoints[d] = numPointsInDim; + } + + if (numPointsCounted != numDataPoints) + { + std::string mesg = gmx::formatString("Could not extract data properly from %s. Wrong data format?" + "\n\n%s", + dataFilename.c_str(), correctFormatMessage.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + + /* The data grid has the data that was read and the properties of the AWH grid */ + for (int d = 0; d < grid.numDimensions(); d++) + { + axis_.push_back(GridAxis(data[d][0], data[d][numDataPoints - 1], + grid.axis(d).period(), numPoints[d])); + } + + /* Map each grid point to a data point. No interpolation, just pick the nearest one. + * It is assumed that the given data is uniformly spaced for each dimension. + */ + for (size_t m = 0; m < grid.numPoints(); m++) + { + /* We only define what we need for the datagrid since it's not needed here which is a bit ugly */ + + if (!valueIsInGrid(grid.point(m).coordValue, axis_)) + { + std::string mesg = + gmx::formatString("%s does not contain data for all coordinate values. " + "Make sure your input data covers the whole sampling domain " + "and is correctly formatted. \n\n%s", + dataFilename.c_str(), correctFormatMessage.c_str()); + GMX_THROW(InvalidInputError(mesg)); + } + (*gridpointToDatapoint)[m] = getNearestIndexInGrid(grid.point(m).coordValue, axis_); + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/grid.h b/src/gromacs/awh/grid.h new file mode 100644 index 0000000000..5db855f33f --- /dev/null +++ b/src/gromacs/awh/grid.h @@ -0,0 +1,389 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * This file contains datatypes and function declarations necessary + * for AWH to interface with the grid code. + * + * The grid organizes spatial properties of the AWH coordinate points. + * This includes traversing points in a specific order, locating + * neighboring points and calculating distances. Multiple dimensions + * as well as periodic dimensions are supported. + * + * \todo: Replace this by a more generic grid class once that is available. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_GRID_H +#define GMX_AWH_GRID_H + +#include +#include + +#include "dimparams.h" /* This is needed for awh_dvec */ + +namespace gmx +{ + +struct AwhDimParams; + +/*! \internal + * \brief An axis, i.e. dimension, of the grid. + */ +class GridAxis +{ + public: + /*! \brief Constructor. + * + * The spacing and number of points are set such that we have + * at least the requested point density. + * Requesting 0 point density results in the minimum number + * of points (2). + * + * \param[in] origin Starting value. + * \param[in] end End value. + * \param[in] period Period, pass 0 if not periodic. + * \param[in] pointDensity Requested number of point per unit of axis length. + */ + GridAxis(double origin, double end, + double period, double pointDensity); + + /*! \brief Constructor. + * + * \param[in] origin Starting value. + * \param[in] end End value. + * \param[in] period Period, pass 0 if not periodic. + * \param[in] numPoints The number of points. + */ + GridAxis(double origin, double end, + double period, int numPoints); + + /*! \brief Returns if the axis has periodic boundaries. + */ + bool isPeriodic() const + { + return period_ > 0; + } + + /*! \brief Returns the period of the grid along the axis. + */ + double period() const + { + return period_; + } + + /*! \brief Returns the grid origin along the axis. + */ + double origin() const + { + return origin_; + } + + /*! \brief Returns the grid point spacing along the axis. + */ + double spacing() const + { + return spacing_; + } + + /*! \brief Returns the number of grid points along the axis. + */ + int numPoints() const + { + return numPoints_; + } + + /*! \brief Returns the period of the grid in points along the axis. + * + * Returns 0 if the axis is not periodic. + */ + int numPointsInPeriod() const + { + return numPointsInPeriod_; + } + + /*! \brief Returns the length of the interval. + * + * This returns the distance obtained by connecting the origin point to + * the end point in the positive direction. Note that this is generally + * not the shortest distance. For period > 0, both origin and + * end are expected to take values in the same periodic interval. + */ + double length() const + { + return length_; + } + + /*! \brief Map a value to the nearest point index along an axis. + * + * \param[in] value Value along the axis. + * \returns the index nearest to the value. + */ + int nearestIndex(double value) const; + + private: + double origin_; /**< Interval start value */ + double length_; /**< Interval length */ + double period_; /**< The period, 0 if not periodic */ + double spacing_; /**< Point spacing */ + int numPoints_; /**< Number of points in the interval */ + int numPointsInPeriod_; /**< Number of points in a period (0 if no periodicity) */ +}; + +/*! \internal + * \brief A point in the grid. + * + * A grid point has a coordinate value and a coordinate index of the same dimensionality as the grid. + * It knows the the linear indices of its neighboring point (which are useful only when handed up to + * the grid). + */ +struct GridPoint +{ + awh_dvec coordValue; /**< Multidimensional coordinate value of this point */ + awh_ivec index; /**< Multidimensional point indices */ + std::vector neighbor; /**< Linear point indices of the neighboring points */ +}; + +/*! \internal + * \brief The grid, generally multidimensional and periodic. + * + * The grid discretizes a multidimensional space with some given resolution. + * Each dimension is represented by an axis which sets the spatial extent, + * point spacing and periodicity of the grid in that direction. + */ +class Grid +{ + private: + /*! \brief Initializes the grid points. + */ + void initPoints(); + + public: + /*! \brief + * The point density per sigma of the Gaussian distribution in an umbrella. + * + * This value should be at least 1 to uniformly cover the reaction coordinate + * range with density and having it larger than 1 does not add information. + */ + static constexpr double c_numPointsPerSigma = 1.0; + + //! Cut-off in sigma for considering points, neglects 4e-8 of the density. + static constexpr double c_scopeCutoff = 5.5; + + /*! \brief Construct a grid using AWH input parameters. + * + * \param[in] dimParams Dimension parameters including the expected inverse variance of the coordinate living on the grid (determines the grid spacing). + * \param[in] awhDimParams Dimension params from inputrec. + */ + Grid(const std::vector &dimParams, + const AwhDimParams *awhDimParams); + + /*! \brief Returns the number of points in the grid. + * + * \returns the number of points in the grid. + */ + size_t numPoints() const + { + return point_.size(); + } + + /*! \brief Returns a reference to a point on the grid. + * + * \returns a constant reference to a point on the grid. + */ + const GridPoint &point(size_t pointIndex) const + { + return point_[pointIndex]; + } + + /*! \brief Returns the dimensionality of the grid. + * + * \returns the dimensionality of the grid. + */ + int numDimensions() const + { + return axis_.size(); + } + + /*! \brief Returns the grid axes. + * + * \returns a constant reference to the grid axes. + */ + const std::vector &axis() const + { + return axis_; + } + + /*! \brief Returns a grid axis. + * + * param[in] dim Dimension to return the grid axis for. + * \returns a constant reference to the grid axis. + */ + const GridAxis &axis(int dim) const + { + return axis_[dim]; + } + + /*! \brief Find the grid point with value nearest to the given value. + * + * \param[in] value Value vector. + * \returns the grid point index. + */ + int nearestIndex(const awh_dvec value) const; + + /*! \brief Query if the value is in the grid. + * + * \param[in] value Value vector. + * \returns true if the value is in the grid. + * \note It is assumed that any periodicity of value has already been taken care of. + */ + bool covers(const awh_dvec value) const; + + private: + std::vector point_; /**< Points on the grid */ + std::vector axis_; /**< Axes, one for each dimension. */ +}; + +/*! \endcond */ + +/*! \brief Convert a multidimensional grid point index to a linear one. + * + * \param[in] grid The grid. + * \param[in] indexMulti Multidimensional grid point index to convert to a linear one. + * \returns the linear index. + */ +int multiDimGridIndexToLinear(const Grid &grid, const awh_ivec indexMulti); + +/*! \brief Convert multidimensional array index to a linear one. + * + * \param[in] indexMulti Multidimensional index to convert to a linear one. + * \param[in] numDim Number of dimensions of the array. + * \param[in] numPointsDim Number of points of the array. + * \returns the linear index. + * \note This function can be used without having an initialized grid. + */ +int multiDimArrayIndexToLinear(const awh_ivec indexMulti, + int numDim, + const awh_ivec numPointsDim); + +/*! \brief Convert a linear grid point index to a multidimensional one. + * + * \param[in] grid The grid. + * \param[in] indexLinear Linear grid point index to convert to a multidimensional one. + * \param[out] indexMulti The multidimensional index. + */ +void linearGridindexToMultiDim(const Grid &grid, + int indexLinear, + awh_ivec indexMulti); + +/*! \brief Convert a linear array index to a multidimensional one. + * + * \param[in] indexLinear Linear array index + * \param[in] ndim Number of dimensions of the array. + * \param[in] numPointsDim Number of points for each dimension. + * \param[out] indexMulti The multidimensional index. + */ +void linearArrayIndexToMultiDim(int indexLinear, + int ndim, + const awh_ivec numPointsDim, + awh_ivec indexMulti); + +/*! \brief + * Find the next grid point in the sub-part of the grid given a starting point. + * + * The given grid point index is updated to the next valid grid point index + * by traversing the sub-part of the grid, here termed the subgrid. + * Since the subgrid range might extend beyond the actual size of the grid, + * the subgrid is traversed until a point both in the subgrid and grid is + * found. If no point is found, the function returns false and the index is + * not modified. The starting point needs to be inside of the subgrid. However, + * if this index is not given, meaning < 0, then the search is initialized at + * the subgrid origin, i.e. in this case the "next" grid point index is + * defined to be the first common grid/subgrid point. + * + * \param[in] grid The grid. + * \param[in] subgridOrigin Vector locating the subgrid origin relative to the grid origin. + * \param[in] subgridNpoints Number of points along each subgrid dimension. + * \param[in,out] gridPointIndex Pointer to the starting/next grid point index. + * \returns true if the grid point was updated. + */ +bool advancePointInSubgrid(const Grid &grid, + const awh_ivec subgridOrigin, + const awh_ivec subgridNpoints, + int *gridPointIndex); + +/*! \brief Maps each point in the grid to a point in the data grid. + * + * This functions maps an AWH bias grid to a user provided input data grid. + * The value of data grid point i along dimension d is given by data[d][i]. + * The number of dimensions of the data should equal that of the grid. + * A fatal error is thrown if extracting the data fails or the data does not cover the whole grid. + * + * \param[out] gridpointToDatapoint Array mapping each grid point to a data point index. + * \param[in] data 2D array in format ndim x ndatapoints with data grid point values. + * \param[in] numDataPoints Number of data points. + * \param[in] dataFilename The data filename. + * \param[in] grid The grid. + * \param[in] correctFormatMessage String to include in error message if extracting the data fails. + */ +void mapGridToDataGrid(std::vector *gridpointToDatapoint, + const double* const *data, + int numDataPoints, + const std::string &dataFilename, + const Grid &grid, + const std::string &correctFormatMessage); + +/*! \brief + * Get the deviation along one dimension from the given value to a point in the grid. + * + * \param[in] grid The grid. + * \param[in] dimIndex Dimensional index in [0, ndim -1]. + * \param[in] pointIndex Grid point index. + * \param[in] value Value along the given dimension. + * \returns the deviation of the given value to the given point. + */ +double getDeviationFromPointAlongGridAxis(const Grid &grid, + int dimIndex, + int pointIndex, + double value); + +} // namespace gmx + +#endif diff --git a/src/gromacs/awh/histogramsize.cpp b/src/gromacs/awh/histogramsize.cpp new file mode 100644 index 0000000000..886e8b5d37 --- /dev/null +++ b/src/gromacs/awh/histogramsize.cpp @@ -0,0 +1,287 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the HistogramSize class. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "histogramsize.h" + +#include +#include +#include + +#include + +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/stringutil.h" + +#include "biasparams.h" +#include "pointstate.h" + +namespace gmx +{ + +HistogramSize::HistogramSize(const AwhBiasParams &awhBiasParams, + double histogramSizeInitial) : + numUpdates_(0), + histogramSize_(histogramSizeInitial), + inInitialStage_(awhBiasParams.eGrowth == eawhgrowthEXP_LINEAR), + equilibrateHistogram_(awhBiasParams.equilibrateHistogram), + logScaledSampleWeight_(0), + maxLogScaledSampleWeight_(0), + havePrintedAboutCovering_(false) +{ +} + +double HistogramSize::newHistogramSizeInitialStage(const BiasParams ¶ms, + double t, + bool detectedCovering, + ArrayRef weightsumCovering, + FILE *fplog) +{ + /* The histogram size is kept constant until the sampling region has been covered + and the the current sample weight is large enough and the histogram is ready. */ + if (!detectedCovering || + (logScaledSampleWeight_ < maxLogScaledSampleWeight_) || + equilibrateHistogram_) + { + return histogramSize_; + } + + /* Reset the covering weight histogram. If we got this far we are either entering a + new covering stage with a new covering histogram or exiting the initial stage + altogether. */ + std::fill(weightsumCovering.begin(), weightsumCovering.end(), 0); + + /* The current sample weigth is now the maximum. */ + double prevMaxLogScaledSampleWeight = maxLogScaledSampleWeight_; + maxLogScaledSampleWeight_ = logScaledSampleWeight_; + + /* Increase the histogram size by a constant scale factor if we can, i.e. if the sample weight + resulting from such a scaling is still larger than the previous maximum sample weight + (ensuring that the sample weights at the end of each covering stage are monotonically + increasing). If we cannot, exit the initial stage without changing the histogram size. */ + + /* The scale factor. The value is not very critical but should obviously be > 1 (or the exit + will happen very late) and probably < 5 or so (or there will be no initial stage). */ + static const double growthFactor = 3; + + /* The scale factor is in most cases very close to the histogram growth factor. */ + double scaleFactor = growthFactor/(1. + params.updateWeight*params.localWeightScaling/histogramSize_); + + bool exitInitialStage = (logScaledSampleWeight_ - std::log(scaleFactor) <= prevMaxLogScaledSampleWeight); + double newHistogramSize = exitInitialStage ? histogramSize_ : histogramSize_*growthFactor; + + /* Update the AWH bias about the exit. */ + inInitialStage_ = !exitInitialStage; + + /* Print information about coverings and if there was an exit. */ + if (fplog != nullptr) + { + std::string prefix = gmx::formatString("\nawh%d:", params.biasIndex + 1); + fprintf(fplog, "%s covering at t = %g ps. Decreased the update size.\n", prefix.c_str(), t); + + if (exitInitialStage) + { + fprintf(fplog, "%s out of the initial stage at t = %g.\n", prefix.c_str(), t); + /* It would be nice to have a way of estimating a minimum time until exit but it + is difficult because the exit time is determined by how long it takes to cover + relative to the time it takes to "regaining" enough sample weight. The latter + is easy to calculate, but how the former depends on the histogram size + is not known. */ + } + fflush(fplog); + } + return newHistogramSize; +} + +namespace +{ + +/*! \brief + * Checks if the histogram has equilibrated to the target distribution. + * + * The histogram is considered equilibrated if, for a minimum fraction of + * the target region, the relative error of the sampled weight relative + * to the target is less than a tolerance value. + * + * \param[in] pointStates The state of the bias points. + * \returns true if the histogram is equilibrated. + */ +bool histogramIsEquilibrated(const std::vector &pointStates) +{ + /* Get the total weight of the total weight histogram; needed for normalization. */ + double totalWeight = 0; + int numTargetPoints = 0; + for (auto &pointState : pointStates) + { + if (!pointState.inTargetRegion()) + { + continue; + } + totalWeight += pointState.weightSumTot(); + numTargetPoints++; + } + GMX_RELEASE_ASSERT(totalWeight > 0, "No samples when normalizing AWH histogram."); + double inverseTotalWeight = 1./totalWeight; + + /* Points with target weight below a certain cutoff are ignored. */ + static const double minTargetCutoff = 0.05; + double minTargetWeight = 1./numTargetPoints*minTargetCutoff; + + /* Points with error less than this tolerance pass the check.*/ + static const double errorTolerance = 0.2; + + /* Sum up weight of points that do or don't pass the check. */ + double equilibratedWeight = 0; + double notEquilibratedWeight = 0; + for (auto &pointState : pointStates) + { + double targetWeight = pointState.target(); + double sampledWeight = pointState.weightSumTot()*inverseTotalWeight; + + /* Ignore these points. */ + if (!pointState.inTargetRegion() || targetWeight < minTargetWeight) + { + continue; + } + + if (std::abs(sampledWeight/targetWeight - 1) > errorTolerance) + { + notEquilibratedWeight += targetWeight; + } + else + { + equilibratedWeight += targetWeight; + } + } + + /* It is enough if sampling in at least a fraction of the target region follows the target + distribution. Boundaries will in general fail and this should be ignored (to some extent). */ + static const double minFraction = 0.8; + + return equilibratedWeight/(equilibratedWeight + notEquilibratedWeight) > minFraction;; +} + +} // namespace + +double HistogramSize::newHistogramSize(const BiasParams ¶ms, + double t, + bool covered, + const std::vector &pointStates, + ArrayRef weightsumCovering, + FILE *fplog) +{ + double newHistogramSize; + if (inInitialStage_) + { + /* Only bother with checking equilibration if we have covered already. */ + if (equilibrateHistogram_ && covered) + { + /* The histogram is equilibrated at most once. */ + equilibrateHistogram_ = !histogramIsEquilibrated(pointStates); + + std::string prefix = gmx::formatString("\nawh%d:", params.biasIndex + 1); + if (!equilibrateHistogram_) + { + fprintf(fplog, "%s equilibrated histogram at t = %g ps.\n", prefix.c_str(), t); + } + else if (!havePrintedAboutCovering_) + { + fprintf(fplog, "%s covered but histogram not equilibrated at t = %g ps.\n", prefix.c_str(), t); + havePrintedAboutCovering_ = true; /* Just print once. */ + } + } + + /* In the initial stage, the histogram grows dynamically as a function of the number of coverings. */ + newHistogramSize = newHistogramSizeInitialStage(params, t, covered, weightsumCovering, fplog); + } + else + { + /* If not in the initial stage, the histogram grows at a linear, possibly scaled down, rate. */ + newHistogramSize = histogramSize_ + params.updateWeight*params.localWeightScaling; + } + + return newHistogramSize; +} + +void HistogramSize::setHistogramSize(double histogramSize, + double weightHistogramScalingFactor) +{ + GMX_ASSERT(histogramSize > 0, "The histogram should not be empty"); + GMX_ASSERT(weightHistogramScalingFactor > 0, "The histogram scaling factor should be positive"); + + histogramSize_ = histogramSize; + + /* The weight of new samples relative to previous ones change + * when the histogram is rescaled. We keep the log since this number + * can become very large. + */ + logScaledSampleWeight_ -= std::log(weightHistogramScalingFactor); +}; + +void HistogramSize::restoreFromHistory(const AwhBiasStateHistory &stateHistory) +{ + numUpdates_ = stateHistory.numUpdates; + histogramSize_ = stateHistory.histSize; + inInitialStage_ = stateHistory.in_initial; + equilibrateHistogram_ = stateHistory.equilibrateHistogram; + logScaledSampleWeight_ = stateHistory.logScaledSampleWeight; + maxLogScaledSampleWeight_ = stateHistory.maxLogScaledSampleWeight; + havePrintedAboutCovering_ = false; +} + +void HistogramSize::storeState(AwhBiasStateHistory *stateHistory) const +{ + stateHistory->numUpdates = numUpdates_; + stateHistory->histSize = histogramSize_; + stateHistory->in_initial = inInitialStage_; + stateHistory->equilibrateHistogram = equilibrateHistogram_; + stateHistory->logScaledSampleWeight = logScaledSampleWeight_; + stateHistory->maxLogScaledSampleWeight = maxLogScaledSampleWeight_; + /* We'll print again about covering when restoring the state */ +} + +} // namespace gmx diff --git a/src/gromacs/awh/histogramsize.h b/src/gromacs/awh/histogramsize.h new file mode 100644 index 0000000000..0adf36ca59 --- /dev/null +++ b/src/gromacs/awh/histogramsize.h @@ -0,0 +1,198 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares the HistogramSize class. + * + * The data members of this class keep track of global size and update related + * properties of the bias histogram and the evolution of the histogram size. + * Initially histogramSize_ (and thus the convergence rate) is controlled + * heuristically to get good initial estimates, i.e. increase the robustness + * of the method. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_HISTOGRAMSIZE_H +#define GMX_AWH_HISTOGRAMSIZE_H + +#include + +#include + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/arrayref.h" + +namespace gmx +{ + +struct AwhBiasStateHistory; +struct AwhBiasParams; +class BiasParams; +class PointState; + +/*! \internal + * \brief Tracks global size related properties of the bias histogram. + * + * Tracks the number of updates and the histogram size. + * Also keep track of the stage (initial/final of the AWH method + * and printing warnings about covering. + * + * \note Histogram sizes are floating-point values, since the histogram uses weighted + * entries and we can assign a floating-point scaling factor when changing it. + */ +class HistogramSize +{ + public: + /*! \brief Constructor. + * + * \param[in] awhBiasParams The Bias parameters from inputrec. + * \param[in] histogramSizeInitial The initial histogram size. + */ + HistogramSize(const AwhBiasParams &awhBiasParams, + double histogramSizeInitial); + + private: + /*! \brief + * Returns the new size of the reference weight histogram in the initial stage. + * + * This function also takes care resetting the histogram used for covering checks + * and for exiting the initial stage. + * + * \param[in] params The bias parameters. + * \param[in] t Time. + * \param[in] detectedCovering True if we detected that the sampling interval has been sufficiently covered. + * \param[in,out] weightsumCovering The weight sum for checking covering. + * \param[in,out] fplog Log file. + * \returns the new histogram size. + */ + double newHistogramSizeInitialStage(const BiasParams ¶ms, + double t, + bool detectedCovering, + ArrayRef weightsumCovering, + FILE *fplog); + + public: + /*! \brief + * Return the new reference weight histogram size for the current update. + * + * This function also takes care of checking for covering in the initial stage. + * + * \param[in] params The bias parameters. + * \param[in] t Time. + * \param[in] covered True if the sampling interval has been covered enough. + * \param[in] pointStates The state of the grid points. + * \param[in,out] weightsumCovering The weight sum for checking covering. + * \param[in,out] fplog Log file. + * \returns the new histogram size. + */ + double newHistogramSize(const BiasParams ¶ms, + double t, + bool covered, + const std::vector &pointStates, + ArrayRef weightsumCovering, + FILE *fplog); + + /*! \brief Restores the histogram size from history. + * + * \param[in] stateHistory The AWH bias state history. + */ + void restoreFromHistory(const AwhBiasStateHistory &stateHistory); + + /*! \brief Store the histogram size state in a history struct. + * + * \param[in,out] stateHistory The AWH bias state history. + */ + void storeState(AwhBiasStateHistory *stateHistory) const; + + /*! \brief Returns the number of updates since the start of the simulation. + */ + int numUpdates() const + { + return numUpdates_; + }; + + /*! \brief Increments the number of updates by 1. + */ + void incrementNumUpdates() + { + numUpdates_ += 1; + } + + /*! \brief Returns the histogram size. + */ + double histogramSize() const + { + return histogramSize_; + }; + + /*! \brief Sets the histogram size. + * + * \param[in] histogramSize The new histogram size. + * \param[in] weightHistogramScalingFactor The factor to scale the weight by. + */ + void setHistogramSize(double histogramSize, + double weightHistogramScalingFactor); + + /*! \brief Returns true if we are in the initial stage of the AWH method. + */ + inline bool inInitialStage() const + { + return inInitialStage_; + }; + + private: + gmx_int64_t numUpdates_; /**< The number of updates performed since the start of the simulation. */ + + /* The histogram size sets the update size and so controls the convergence rate of the free energy and bias. */ + double histogramSize_; /**< Size of reference weight histogram. */ + + /* Values that control the evolution of the histogram size. */ + bool inInitialStage_; /**< True if in the intial stage. */ + bool equilibrateHistogram_; /**< True if samples are kept from accumulating until the sampled distribution is close enough to the target. */ + double logScaledSampleWeight_; /**< The log of the current sample weight, scaled because of the histogram rescaling. */ + double maxLogScaledSampleWeight_; /**< Maximum sample weight obtained for previous (smaller) histogram sizes. */ + + /* Bool to avoid printing multiple, not so useful, messages to log */ + bool havePrintedAboutCovering_; /**< True if we have printed about covering to the log while equilibrateHistogram==true */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_HISTOGRAMSIZE_H */ diff --git a/src/gromacs/awh/pointstate.cpp b/src/gromacs/awh/pointstate.cpp new file mode 100644 index 0000000000..67d312ff35 --- /dev/null +++ b/src/gromacs/awh/pointstate.cpp @@ -0,0 +1,78 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * \brief + * Implements the one method of the PointState class called only for one point per step. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#include "gmxpre.h" + +#include "pointstate.h" + +namespace gmx +{ + +namespace +{ + +/*! \brief Returns the exponent c where exp(c) = exp(a) + exp(b). + * + * \param[in] a First exponent. + * \param[in] b Second exponent. + * \returns c. + */ +double expSum(double a, + double b) +{ + return (a > b ? a : b) + std::log1p(std::exp(-std::fabs(a - b))); +} + +} // namespace + +void PointState::samplePmf(double convolvedBias) +{ + if (inTargetRegion()) + { + logPmfSum_ = expSum(logPmfSum_, -convolvedBias); + numVisitsIteration_ += 1; + } +} + +} // namespace gmx diff --git a/src/gromacs/awh/pointstate.h b/src/gromacs/awh/pointstate.h new file mode 100644 index 0000000000..288c8da7bf --- /dev/null +++ b/src/gromacs/awh/pointstate.h @@ -0,0 +1,521 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \internal \file + * + * \brief + * Declares and defines the PointState class. + * + * Since nearly all operations on PointState objects occur in loops over + * (parts of) the grid of an AWH bias, all these methods should be inlined. + * Only samplePmf() is called only once per step and is thus not inlined. + * + * \author Viveca Lindahl + * \author Berk Hess + * \ingroup module_awh + */ + +#ifndef GMX_AWH_POINTSTATE_H +#define GMX_AWH_POINTSTATE_H + +#include + +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/gmxassert.h" + +#include "biasparams.h" + +namespace gmx +{ + +namespace +{ + +//! A value that can be passed to exp() with result 0, also with SIMD +static constexpr double c_largeNegativeExponent = -10000.0; + +//! The largest acceptable positive exponent for variables that are passed to exp(). +static constexpr double c_largePositiveExponent = 700.0; + +} // namepace + +/*! \internal + * \brief The state of a coordinate point. + * + * This class contains all the state variables of a coordinate point + * (on the bias grid) and methods to update the state of a point. + */ +class PointState +{ + public: + /*! \brief Constructs a point state with default values. */ + PointState() : bias_(0), + freeEnergy_(0), + target_(1), + targetConstantWeight_(1), + weightSumIteration_(0), + weightSumTot_(0), + weightSumRef_(1), + lastUpdateIndex_(0), + logPmfSum_(0), + numVisitsIteration_(0), + numVisitsTot_(0) + { + }; + + /*! \brief + * Set all values in the state to those from a history. + * + * \param[in] psh Coordinate point history to copy from. + */ + void setFromHistory(const AwhPointStateHistory &psh) + { + target_ = psh.target; + freeEnergy_ = psh.free_energy; + bias_ = psh.bias; + weightSumIteration_ = psh.weightsum_iteration; + weightSumTot_ = psh.weightsum_tot; + weightSumRef_ = psh.weightsum_ref; + lastUpdateIndex_ = psh.last_update_index; + logPmfSum_ = psh.log_pmfsum; + numVisitsIteration_ = psh.visits_iteration; + numVisitsTot_ = psh.visits_tot; + } + + /*! \brief + * Store the state of a point in a history struct. + * + * \param[in,out] psh Coordinate point history to copy to. + */ + void storeState(AwhPointStateHistory *psh) const + { + psh->target = target_; + psh->free_energy = freeEnergy_; + psh->bias = bias_; + psh->weightsum_iteration = weightSumIteration_; + psh->weightsum_tot = weightSumTot_; + psh->weightsum_ref = weightSumRef_; + psh->last_update_index = lastUpdateIndex_; + psh->log_pmfsum = logPmfSum_; + psh->visits_iteration = numVisitsIteration_; + psh->visits_tot = numVisitsTot_; + } + + /*! \brief + * Query if the point is in the target region. + * + * \returns true if the point is in the target region. + */ + bool inTargetRegion() const + { + return target_ > 0; + } + + /*! \brief Return the bias function estimate. */ + double bias() const + { + return bias_; + } + + /*! \brief Set the target to zero and the bias to minus infinity. */ + void setTargetToZero() + { + target_ = 0; + /* the bias = log(target) + const = -infty */ + bias_ = c_largeNegativeExponent; + } + + /*! \brief Return the free energy. */ + double freeEnergy() const + { + return freeEnergy_; + } + + /*! \brief Set the free energy, only to be used at initialization. + * + * \param[in] freeEnergy The free energy. + */ + void setFreeEnergy(double freeEnergy) + { + freeEnergy_ = freeEnergy; + } + + /*! \brief Return the target distribution value. */ + double target() const + { + return target_; + } + + /*! \brief Return the weight accumulated since the last update. */ + double weightSumIteration() const + { + return weightSumIteration_; + } + + /*! \brief Increases the weight accumulated since the last update. + * + * \param[in] weight The amount to add to the weight + */ + void increaseWeightSumIteration(double weight) + { + weightSumIteration_ += weight; + } + + /*! \brief Returns the accumulated weight */ + double weightSumTot() const + { + return weightSumTot_; + } + + /*! \brief Return the reference weight histogram. */ + double weightSumRef() const + { + return weightSumRef_; + } + + /*! \brief Return log(PmfSum). */ + double logPmfSum() const + { + return logPmfSum_; + } + + /*! \brief Set log(PmfSum). + * + * TODO: Replace this setter function with a more elegant solution. + * + * \param[in] logPmfSum The log(PmfSum). + */ + void setLogPmfSum(double logPmfSum) + { + logPmfSum_ = logPmfSum; + } + + /*! \brief Return the number of visits since the last update */ + double numVisitsIteration() const + { + return numVisitsIteration_; + } + + /*! \brief Return the total number of visits */ + double numVisitsTot() const + { + return numVisitsTot_; + } + + /*! \brief Set the constant target weight factor. + * + * \param[in] targetConstantWeight The target weight factor. + */ + void setTargetConstantWeight(double targetConstantWeight) + { + targetConstantWeight_ = targetConstantWeight; + } + + /*! \brief Updates the bias of a point. */ + void updateBias() + { + GMX_ASSERT(target_ > 0, "AWH target distribution must be > 0 to calculate the point bias."); + + bias_ = freeEnergy() + std::log(target_); + } + + /*! \brief Set the initial reference weighthistogram. + * + * \param[in] histogramSize The weight histogram size. + */ + void setInitialReferenceWeightHistogram(double histogramSize) + { + weightSumRef_ = histogramSize*target_; + } + + /*! \brief Correct free energy and PMF sum for the change in minimum. + * + * \param[in] minimumFreeEnergy The free energy at the minimum; + */ + void normalizeFreeEnergyAndPmfSum(double minimumFreeEnergy) + { + if (inTargetRegion()) + { + /* The sign of the free energy and PMF constants are opposite + * because the PMF samples are reweighted with the negative + * bias e^(-bias) ~ e^(-free energy). + */ + freeEnergy_ -= minimumFreeEnergy; + logPmfSum_ += minimumFreeEnergy; + } + } + + /*! \brief Apply previous updates that were skipped. + * + * An update can only be skipped if the parameters needed for the update are constant or + * deterministic so that the same update can be performed at a later time. + * Here, the necessary parameters are the sampled weight and scaling factors for the + * histograms. The scaling factors are provided as arguments only to avoid recalculating + * them for each point + * + * The last update index is also updated here. + * + * \param[in] params The AWH bias parameters. + * \param[in] numUpdates The global number of updates. + * \param[in] weighthistScaling Scale factor for the reference weight histogram. + * \param[in] logPmfSumScaling Scale factor for the reference PMF histogram. + * \returns true if at least one update was applied. + */ + bool performPreviouslySkippedUpdates(const BiasParams ¶ms, + gmx_int64_t numUpdates, + double weighthistScaling, + double logPmfSumScaling) + { + GMX_ASSERT(params.skipUpdates(), "Calling function for skipped updates when skipping updates is not allowed"); + + if (!inTargetRegion()) + { + return false; + } + + /* The most current past update */ + gmx_int64_t lastUpdateIndex = numUpdates; + gmx_int64_t numUpdatesSkipped = lastUpdateIndex - lastUpdateIndex_; + + if (numUpdatesSkipped == 0) + { + /* Was not updated */ + return false; + } + + for (int i = 0; i < numUpdatesSkipped; i++) + { + /* This point was non-local at the time of the update meaning no weight */ + updateFreeEnergyAndWeight(params, 0, weighthistScaling, logPmfSumScaling); + } + + /* Only past updates are applied here. */ + lastUpdateIndex_ = lastUpdateIndex; + + return true; + } + + /*! \brief Apply a point update with new sampling. + * + * \note The last update index is also updated here. + * \note The new sampling containers are cleared here. + * + * \param[in] params The AWH bias parameters. + * \param[in] numUpdates The global number of updates. + * \param[in] weighthistScaling Scaling factor for the reference weight histogram. + * \param[in] logPmfSumScaling Log of the scaling factor for the PMF histogram. + */ + void updateWithNewSampling(const BiasParams ¶ms, + gmx_int64_t numUpdates, + double weighthistScaling, + double logPmfSumScaling) + { + GMX_RELEASE_ASSERT(lastUpdateIndex_ == numUpdates, "When doing a normal update, the point update index should match the global index, otherwise we lost (skipped?) updates."); + + updateFreeEnergyAndWeight(params, weightSumIteration_, weighthistScaling, logPmfSumScaling); + lastUpdateIndex_ += 1; + + /* Clear the iteration collection data */ + weightSumIteration_ = 0; + numVisitsIteration_ = 0; + } + + + /*! \brief Update the PMF histogram with the current coordinate value. + * + * \param[in] convolvedBias The convolved bias. + */ + void samplePmf(double convolvedBias); + + private: + /*! \brief Update the free energy estimate of a point. + * + * The free energy update here is inherently local, i.e. it just depends on local sampling and on constant + * AWH parameters. This assumes that the variables used here are kept constant, at least in between + * global updates. + * + * \param[in] params The AWH bias parameters. + * \param[in] weightAtPoint Sampled probability weight at this point. + */ + void updateFreeEnergy(const BiasParams ¶ms, + double weightAtPoint) + { + double weighthistSampled = weightSumRef() + weightAtPoint; + double weighthistTarget = weightSumRef() + params.updateWeight*target_; + + double df = -std::log(weighthistSampled/weighthistTarget); + freeEnergy_ += df; + + GMX_RELEASE_ASSERT(std::abs(freeEnergy_) < c_largePositiveExponent, + "Very large free energy differences or badly normalized free energy in AWH update."); + } + + /*! \brief Update the reference weight histogram of a point. + * + * \param[in] params The AWH bias parameters. + * \param[in] weightAtPoint Sampled probability weight at this point. + * \param[in] scaleFactor Factor to rescale the histogram with. + */ + void updateWeightHistogram(const BiasParams ¶ms, + double weightAtPoint, + double scaleFactor) + { + if (params.idealWeighthistUpdate) + { + /* Grow histogram using the target distribution. */ + weightSumRef_ += target_*params.updateWeight*params.localWeightScaling; + } + else + { + /* Grow using the actual samples (which are distributed ~ as target). */ + weightSumRef_ += weightAtPoint*params.localWeightScaling; + } + + weightSumRef_ *= scaleFactor; + } + + /*! \brief Apply a point update. + * + * This updates local properties that can be updated without + * accessing or affecting all points. + * This excludes updating the size of reference weight histogram and + * the target distribution. The bias update is excluded only because + * if updates have been skipped this function will be called multiple + * times, while the bias only needs to be updated once (last). + * + * Since this function only performs the update with the given + * arguments and does not know anything about the time of the update, + * the last update index is not updated here. The caller should take + * care of updating the update index. + * + * \param[in] params The AWH bias parameters. + * \param[in] weightAtPoint Sampled probability weight at this point. + * \param[in] weighthistScaling Scaling factor for the reference weight histogram. + * \param[in] logPmfSumScaling Log of the scaling factor for the PMF histogram. + */ + void updateFreeEnergyAndWeight(const BiasParams ¶ms, + double weightAtPoint, + double weighthistScaling, + double logPmfSumScaling) + { + updateFreeEnergy(params, weightAtPoint); + updateWeightHistogram(params, weightAtPoint, weighthistScaling); + logPmfSum_ += logPmfSumScaling; + } + + + public: + /*! \brief Update the target weight of a point. + * + * Note that renormalization over all points is needed after the update. + * + * \param[in] params The AWH bias parameters. + * \param[in] freeEnergyCutoff The cut-off for the free energy for target type "cutoff". + * \returns the updated value of the target. + */ + double updateTargetWeight(const BiasParams ¶ms, + double freeEnergyCutoff) + { + switch (params.eTarget) + { + case eawhtargetCONSTANT: + target_ = 1; + break; + case eawhtargetCUTOFF: + { + double df = freeEnergy_ - freeEnergyCutoff; + target_ = 1/(1 + std::exp(df)); + break; + } + case eawhtargetBOLTZMANN: + target_ = std::exp(-params.temperatureScaleFactor*freeEnergy_); + break; + case eawhtargetLOCALBOLTZMANN: + target_ = weightSumRef_; + break; + } + + /* All target types can be modulated by a constant factor. */ + target_ *= targetConstantWeight_; + + return target_; + } + + /*! \brief Set the weight and count accumulated since the last update. + * + * \param[in] weightSum The weight-sum value + * \param[in] numVisits The number of visits + */ + void setPartialWeightAndCount(double weightSum, + double numVisits) + { + weightSumIteration_ = weightSum; + numVisitsIteration_ = numVisits; + } + + /*! \brief Add the weights and counts accumulated between updates. */ + void addPartialWeightAndCount() + { + weightSumTot_ += weightSumIteration_; + numVisitsTot_ += numVisitsIteration_; + } + + /*! \brief Scale the target weight of the point. + * + * \param[in] scaleFactor Factor to scale with. + */ + void scaleTarget(double scaleFactor) + { + target_ *= scaleFactor; + } + + private: + double bias_; /**< Current biasing function estimate */ + double freeEnergy_; /**< Current estimate of the convolved free energy/PMF. */ + double target_; /**< Current target distribution, normalized to 1 */ + double targetConstantWeight_; /**< Constant target weight, from user data. */ + double weightSumIteration_; /**< Accumulated weight this iteration; note: only contains data for this Bias, even when sharing biases. */ + double weightSumTot_; /**< Accumulated weights, never reset */ + double weightSumRef_; /**< The reference weight histogram determining the free energy updates */ + gmx_int64_t lastUpdateIndex_; /**< The last update that was performed at this point, in units of number of updates. */ + double logPmfSum_; /**< Logarithm of the PMF histogram */ + double numVisitsIteration_; /**< Visits to this bin this iteration; note: only contains data for this Bias, even when sharing biases. */ + double numVisitsTot_; /**< Accumulated visits to this bin */ +}; + +} // namespace gmx + +#endif /* GMX_AWH_POINTSTATE_H */ diff --git a/src/gromacs/awh/read-params.cpp b/src/gromacs/awh/read-params.cpp new file mode 100644 index 0000000000..59f85f7543 --- /dev/null +++ b/src/gromacs/awh/read-params.cpp @@ -0,0 +1,801 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 1991-2000, University of Groningen, The Netherlands. + * Copyright (c) 2001-2004, The GROMACS development team. + * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +#include "gmxpre.h" + +#include "read-params.h" + +#include "gromacs/awh/awh.h" +#include "gromacs/fileio/readinp.h" +#include "gromacs/fileio/warninp.h" +#include "gromacs/math/units.h" +#include "gromacs/math/utilities.h" +#include "gromacs/math/vec.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/mdtypes/inputrec.h" +#include "gromacs/mdtypes/md_enums.h" +#include "gromacs/mdtypes/pull-params.h" +#include "gromacs/pbcutil/pbc.h" +#include "gromacs/pulling/pull.h" +#include "gromacs/random/seed.h" +#include "gromacs/utility/cstringutil.h" +#include "gromacs/utility/fatalerror.h" +#include "gromacs/utility/smalloc.h" + +#include "biasparams.h" +#include "biassharing.h" + +namespace gmx +{ + +const char *eawhtarget_names[eawhtargetNR+1] = { + "constant", "cutoff", "boltzmann", "local-boltzmann", nullptr +}; + +const char *eawhgrowth_names[eawhgrowthNR+1] = { + "exp-linear", "linear", nullptr +}; + +const char *eawhpotential_names[eawhpotentialNR+1] = { + "convolved", "umbrella", nullptr +}; + +const char *eawhcoordprovider_names[eawhcoordproviderNR+1] = { + "pull", nullptr +}; + +/*! \brief + * Read parameters of an AWH bias dimension. + * + * \param[in,out] ninp_p Number of read input file entries. + * \param[in,out] inp_p Input file entries. + * \param[in] prefix Prefix for dimension parameters. + * \param[in,out] dimParams AWH dimensional parameters. + * \param[in] pull_params Pull parameters. + * \param[in,out] wi Struct for bookeeping warnings. + * \param[in] bComment True if comments should be printed. + */ +static void readDimParams(int *ninp_p, t_inpfile **inp_p, const char *prefix, + AwhDimParams *dimParams, const pull_params_t *pull_params, + warninp_t wi, bool bComment) +{ + char warningmsg[STRLEN]; + + int ninp = *ninp_p; + t_inpfile *inp = *inp_p; + + if (bComment) + { + CTYPE("The provider of the reaction coordinate, currently only pull is supported"); + } + char opt[STRLEN]; + sprintf(opt, "%s-coord-provider", prefix); + EETYPE(opt, dimParams->eCoordProvider, eawhcoordprovider_names); + + if (bComment) + { + CTYPE("The coordinate index for this dimension"); + } + sprintf(opt, "%s-coord-index", prefix); + int coordIndexInput; + ITYPE(opt, coordIndexInput, 1); + if (coordIndexInput < 1) + { + gmx_fatal(FARGS, "Failed to read a valid coordinate index for %s. " + "Note that the pull coordinate indexing starts at 1.", opt); + } + + /* The pull coordinate indices start at 1 in the input file, at 0 internally */ + dimParams->coordIndex = coordIndexInput - 1; + + /* The pull settings need to be consistent with the AWH settings */ + if (!(pull_params->coord[dimParams->coordIndex].eType == epullEXTERNAL) ) + { + gmx_fatal(FARGS, "AWH biasing can only be applied to pull type %s", + EPULLTYPE(epullEXTERNAL)); + } + + if (dimParams->coordIndex >= pull_params->ncoord) + { + gmx_fatal(FARGS, "The given AWH coordinate index (%d) is larger than the number of pull coordinates (%d)", + coordIndexInput, pull_params->ncoord); + } + if (pull_params->coord[dimParams->coordIndex].rate != 0) + { + sprintf(warningmsg, "Setting pull-coord%d-rate (%g) is incompatible with AWH biasing this coordinate", coordIndexInput, pull_params->coord[dimParams->coordIndex].rate); + warning_error(wi, warningmsg); + } + + /* Grid params for each axis */ + int eGeom = pull_params->coord[dimParams->coordIndex].eGeom; + + if (bComment) + { + CTYPE("Start and end values for each coordinate dimension"); + } + + sprintf(opt, "%s-start", prefix); + RTYPE(opt, dimParams->origin, 0.); + + sprintf(opt, "%s-end", prefix); + RTYPE(opt, dimParams->end, 0.); + + if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS)) + { + sprintf(warningmsg, "The given interval length given by %s-start (%g) and %s-end (%g) is zero. " + "This will result in only one point along this axis in the coordinate value grid.", + prefix, dimParams->origin, prefix, dimParams->end); + warning(wi, warningmsg); + } + /* Check that the requested interval is in allowed range */ + if (eGeom == epullgDIST) + { + if (dimParams->origin < 0 || dimParams->end < 0) + { + gmx_fatal(FARGS, "%s-start (%g) or %s-end (%g) set to a negative value. With pull geometry distance coordinate values are non-negative. " + "Perhaps you want to use geometry %s instead?", + prefix, dimParams->origin, prefix, dimParams->end, EPULLGEOM(epullgDIR)); + } + } + else if (eGeom == epullgANGLE || eGeom == epullgANGLEAXIS) + { + if (dimParams->origin < 0 || dimParams->end > 180) + { + gmx_fatal(FARGS, "%s-start (%g) and %s-end (%g) are outside of the allowed range 0 to 180 deg for pull geometries %s and %s ", + prefix, dimParams->origin, prefix, dimParams->end, EPULLGEOM(epullgANGLE), EPULLGEOM(epullgANGLEAXIS)); + } + } + else if (eGeom == epullgDIHEDRAL) + { + if (dimParams->origin < -180 || dimParams->end > 180) + { + gmx_fatal(FARGS, "%s-start (%g) and %s-end (%g) are outside of the allowed range -180 to 180 deg for pull geometry %s. ", + prefix, dimParams->origin, prefix, dimParams->end, EPULLGEOM(epullgDIHEDRAL)); + } + } + + if (bComment) + { + CTYPE("The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)"); + } + sprintf(opt, "%s-force-constant", prefix); + RTYPE(opt, dimParams->forceConstant, 0); + if (dimParams->forceConstant <= 0) + { + warning_error(wi, "The force AWH bias force constant should be > 0"); + } + + if (bComment) + { + CTYPE("Estimated diffusion constant (nm^2/ps or rad^2/ps)"); + } + sprintf(opt, "%s-diffusion", prefix); + RTYPE(opt, dimParams->diffusion, 0); + + if (dimParams->diffusion <= 0) + { + const double diffusion_default = 1e-5; + sprintf(warningmsg, "%s not explicitly set by user." + " You can choose to use a default value (%g nm^2/ps or rad^2/ps) but this may very well be non-optimal for your system!", + opt, diffusion_default); + warning(wi, warningmsg); + dimParams->diffusion = diffusion_default; + } + + if (bComment) + { + CTYPE("Diameter that needs to be sampled around a point before it is considered covered."); + } + sprintf(opt, "%s-cover-diameter", prefix); + RTYPE(opt, dimParams->coverDiameter, 0); + + if (dimParams->coverDiameter < 0) + { + gmx_fatal(FARGS, "%s (%g) cannot be negative.", + opt, dimParams->coverDiameter); + } + + *ninp_p = ninp; + *inp_p = inp; +} + +/*! \brief + * Check consistency of input at the AWH bias level. + * + * \param[in] awhBiasParams AWH bias parameters. + * \param[in,out] wi Struct for bookkeeping warnings. + */ +static void checkInputConsistencyAwhBias(const AwhBiasParams &awhBiasParams, + warninp_t wi) +{ + /* Covering diameter and sharing warning. */ + for (int d = 0; d < awhBiasParams.ndim; d++) + { + double coverDiameter = awhBiasParams.dimParams[d].coverDiameter; + if (awhBiasParams.shareGroup <= 0 && coverDiameter > 0) + { + warning(wi, "The covering diameter is only relevant to set for bias sharing simulations."); + } + } +} + +/*! \brief + * Read parameters of an AWH bias. + * + * \param[in,out] ninp_p Number of read input file entries. + * \param[in,out] inp_p Input file entries. + * \param[in,out] awhBiasParams AWH dimensional parameters. + * \param[in] prefix Prefix for bias parameters. + * \param[in] ir Input parameter struct. + * \param[in,out] wi Struct for bookeeping warnings. + * \param[in] bComment True if comments should be printed. + */ +static void read_bias_params(int *ninp_p, t_inpfile **inp_p, AwhBiasParams *awhBiasParams, const char *prefix, + const t_inputrec *ir, warninp_t wi, bool bComment) +{ + int ninp; + t_inpfile *inp; + char opt[STRLEN], prefixdim[STRLEN]; + char warningmsg[STRLEN]; + + /* These are assumed to be declared by the gromacs reading functions */ + ninp = *ninp_p; + inp = *inp_p; + + if (bComment) + { + CTYPE("Estimated initial PMF error (kJ/mol)"); + } + sprintf(opt, "%s-error-init", prefix); + + /* We allow using a default value here without warning (but warn the user if the diffusion constant is not set). */ + RTYPE(opt, awhBiasParams->errorInitial, 10); + if (awhBiasParams->errorInitial <= 0) + { + gmx_fatal(FARGS, "%s (%d) needs to be > 0.", opt); + } + + if (bComment) + { + CTYPE("Growth rate of the reference histogram determining the bias update size: exp-linear or linear"); + } + sprintf(opt, "%s-growth", prefix); + EETYPE(opt, awhBiasParams->eGrowth, eawhgrowth_names); + + if (bComment) + { + CTYPE("Start the simulation by equilibrating histogram towards the target distribution: no or yes"); + } + sprintf(opt, "%s-equilibrate-histogram", prefix); + EETYPE(opt, awhBiasParams->equilibrateHistogram, yesno_names); + if (awhBiasParams->equilibrateHistogram && awhBiasParams->eGrowth != eawhgrowthEXP_LINEAR) + { + sprintf(warningmsg, "Option %s will only have an effect for histogram growth type '%s'.", + opt, EAWHGROWTH(eawhgrowthEXP_LINEAR)); + warning(wi, warningmsg); + } + + if (bComment) + { + CTYPE("Target distribution type: constant, cutoff, boltzmann or local-boltzmann"); + } + sprintf(opt, "%s-target", prefix); + EETYPE(opt, awhBiasParams->eTarget, eawhtarget_names); + + if ((awhBiasParams->eTarget == eawhtargetLOCALBOLTZMANN) && + (awhBiasParams->eGrowth == eawhgrowthEXP_LINEAR)) + { + sprintf(warningmsg, "Target type '%s' combined with histogram growth type '%s' is not " + "expected to give stable bias updates. You probably want to use growth type " + "'%s' instead.", + EAWHTARGET(eawhtargetLOCALBOLTZMANN), EAWHGROWTH(eawhgrowthEXP_LINEAR), + EAWHGROWTH(eawhgrowthLINEAR)); + warning(wi, warningmsg); + } + + if (bComment) + { + CTYPE("Boltzmann beta scaling factor for target distribution types 'boltzmann' and 'boltzmann-local'"); + } + sprintf(opt, "%s-target-beta-scaling", prefix); + RTYPE(opt, awhBiasParams->targetBetaScaling, 0); + + switch (awhBiasParams->eTarget) + { + case eawhtargetBOLTZMANN: + case eawhtargetLOCALBOLTZMANN: + if (awhBiasParams->targetBetaScaling < 0 || awhBiasParams->targetBetaScaling > 1) + { + gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", + opt, awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget)); + } + break; + default: + if (awhBiasParams->targetBetaScaling != 0) + { + gmx_fatal(FARGS, "Value for %s (%g) set explicitly but will not be used for target type %s.", + opt, awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget)); + } + break; + } + + if (bComment) + { + CTYPE("Free energy cutoff value for target distribution type 'cutoff'"); + } + sprintf(opt, "%s-target-cutoff", prefix); + RTYPE(opt, awhBiasParams->targetCutoff, 0); + + switch (awhBiasParams->eTarget) + { + case eawhtargetCUTOFF: + if (awhBiasParams->targetCutoff <= 0) + { + gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", + opt, awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget)); + } + break; + default: + if (awhBiasParams->targetCutoff != 0) + { + gmx_fatal(FARGS, "Value for %s (%g) set explicitly but will not be used for target type %s.", + opt, awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget)); + } + break; + } + + if (bComment) + { + CTYPE("Initialize PMF and target with user data: no or yes"); + } + sprintf(opt, "%s-user-data", prefix); + EETYPE(opt, awhBiasParams->bUserData, yesno_names); + + if (bComment) + { + CTYPE("Group index to share the bias with, 0 means not shared"); + } + sprintf(opt, "%s-share-group", prefix); + ITYPE(opt, awhBiasParams->shareGroup, 0); + if (awhBiasParams->shareGroup < 0) + { + warning_error(wi, "AWH bias share-group should be >= 0"); + } + + if (bComment) + { + CTYPE("Dimensionality of the coordinate"); + } + sprintf(opt, "%s-ndim", prefix); + ITYPE(opt, awhBiasParams->ndim, 0); + + if (awhBiasParams->ndim <= 0 || + awhBiasParams->ndim > c_biasMaxNumDim) + { + gmx_fatal(FARGS, "%s-ndim (%d) needs to be > 0 and at most %d\n", prefix, awhBiasParams->ndim, c_biasMaxNumDim); + } + if (awhBiasParams->ndim > 2) + { + warning_note(wi, "For awh-dim > 2 the estimate based on the diffusion and the initial error is currently only a rough guideline." + " You should verify its usefulness for your system before production runs!"); + } + snew(awhBiasParams->dimParams, awhBiasParams->ndim); + for (int d = 0; d < awhBiasParams->ndim; d++) + { + bComment = bComment && d == 0; + sprintf(prefixdim, "%s-dim%d", prefix, d + 1); + readDimParams(&ninp, &inp, prefixdim, &awhBiasParams->dimParams[d], ir->pull, wi, bComment); + } + + /* Check consistencies here that cannot be checked at read time at a lower level. */ + checkInputConsistencyAwhBias(*awhBiasParams, wi); + + *ninp_p = ninp; + *inp_p = inp; +} + +/*! \brief + * Check consistency of input at the AWH level. + * + * \param[in] awhParams AWH parameters. + * \param[in,out] wi Struct for bookkeeping warnings. + */ +static void checkInputConsistencyAwh(const AwhParams &awhParams, + warninp_t wi) +{ + /* Each pull coord can map to at most 1 AWH coord. + * Check that we have a shared bias when requesting multisim sharing. + */ + bool haveSharedBias = false; + for (int k1 = 0; k1 < awhParams.numBias; k1++) + { + const AwhBiasParams &awhBiasParams1 = awhParams.awhBiasParams[k1]; + + if (awhBiasParams1.shareGroup > 0) + { + haveSharedBias = true; + } + + /* k1 is the reference AWH, k2 is the AWH we compare with (can be equal to k1) */ + for (int k2 = k1; k2 < awhParams.numBias; k2++) + { + for (int d1 = 0; d1 < awhBiasParams1.ndim; d1++) + { + const AwhBiasParams &awhBiasParams2 = awhParams.awhBiasParams[k2]; + + /* d1 is the reference dimension of the reference AWH. d2 is the dim index of the AWH to compare with. */ + for (int d2 = 0; d2 < awhBiasParams2.ndim; d2++) + { + /* Give an error if (d1, k1) is different from (d2, k2) but the pull coordinate is the same */ + if ( (d1 != d2 || k1 != k2) && (awhBiasParams1.dimParams[d1].coordIndex == awhBiasParams2.dimParams[d2].coordIndex) ) + { + char errormsg[STRLEN]; + sprintf(errormsg, "One pull coordinate (%d) cannot be mapped to two separate AWH dimensions (awh%d-dim%d and awh%d-dim%d). " + "If this is really what you want to do you will have to duplicate this pull coordinate.", + awhBiasParams1.dimParams[d1].coordIndex + 1, k1 + 1, d1 + 1, k2 + 1, d2 + 1); + gmx_fatal(FARGS, errormsg); + } + } + } + } + } + + if (awhParams.shareBiasMultisim && !haveSharedBias) + { + warning(wi, "Sharing of biases over multiple simulations is requested, but no bias is marked as shared (share-group > 0)"); + } + + /* mdrun does not support this (yet), but will check again */ + if (haveBiasSharingWithinSimulation(awhParams)) + { + warning(wi, "You have shared biases within a single simulation, but mdrun does not support this (yet)"); + } +} + +AwhParams *readAndCheckAwhParams(int *ninp_p, t_inpfile **inp_p, const t_inputrec *ir, warninp_t wi) +{ + char opt[STRLEN], prefix[STRLEN], prefixawh[STRLEN]; + + AwhParams *awhParams; + snew(awhParams, 1); + + int ninp = *ninp_p; + t_inpfile *inp = *inp_p; + + sprintf(prefix, "%s", "awh"); + + /* Parameters common for all biases */ + + CTYPE("The way to apply the biasing potential: convolved or umbrella"); + sprintf(opt, "%s-potential", prefix); + EETYPE(opt, awhParams->ePotential, eawhpotential_names); + + CTYPE("The random seed used for sampling the umbrella center in the case of umbrella type potential"); + sprintf(opt, "%s-seed", prefix); + ITYPE(opt, awhParams->seed, -1); + if (awhParams->seed == -1) + { + awhParams->seed = static_cast(gmx::makeRandomSeed()); + fprintf(stderr, "Setting the AWH bias MC random seed to %" GMX_PRId64 "\n", awhParams->seed); + } + + CTYPE("Data output interval in number of steps"); + sprintf(opt, "%s-nstout", prefix); + ITYPE(opt, awhParams->nstOut, 100000); + if (awhParams->nstOut <= 0) + { + char buf[STRLEN]; + sprintf(buf, "Not writing AWH output with AWH (%s = %d) does not make sense", + opt, awhParams->nstOut); + warning_error(wi, buf); + } + + CTYPE("Coordinate sampling interval in number of steps"); + sprintf(opt, "%s-nstsample", prefix); + ITYPE(opt, awhParams->nstSampleCoord, 10); + + CTYPE("Free energy and bias update interval in number of samples"); + sprintf(opt, "%s-nsamples-update", prefix); + ITYPE(opt, awhParams->numSamplesUpdateFreeEnergy, 10); + + CTYPE("When true, biases with share-group>0 are shared between multiple simulations"); + sprintf(opt, "%s-share-multisim", prefix); + EETYPE(opt, awhParams->shareBiasMultisim, yesno_names); + + CTYPE("The number of independent AWH biases"); + sprintf(opt, "%s-nbias", prefix); + ITYPE(opt, awhParams->numBias, 1); + if (awhParams->numBias <= 0) + { + gmx_fatal(FARGS, "%s needs to be an integer > 0", opt); + } + + /* Read the parameters specific to each AWH bias */ + snew(awhParams->awhBiasParams, awhParams->numBias); + + for (int k = 0; k < awhParams->numBias; k++) + { + bool bComment = (k == 0); + sprintf(prefixawh, "%s%d", prefix, k + 1); + read_bias_params(&ninp, &inp, &awhParams->awhBiasParams[k], prefixawh, ir, wi, bComment); + } + + /* Do a final consistency check before returning */ + checkInputConsistencyAwh(*awhParams, wi); + + if (ir->init_step != 0) + { + warning_error(wi, "With AWH init-step should be 0"); + } + + *ninp_p = ninp; + *inp_p = inp; + + return awhParams; +} + +/*! \brief + * Gets the period of a pull coordinate. + * + * \param[in] pull_params Pull parameters. + * \param[in] coord_ind Pull coordinate index. + * \param[in] box Box vectors. + * \returns the period (or 0 if not periodic). + */ +static double get_pull_coord_period(const pull_params_t *pull_params, + int coord_ind, + const matrix box) +{ + double period; + t_pull_coord *pcrd_params = &pull_params->coord[coord_ind]; + + if (pcrd_params->eGeom == epullgDIRPBC) + { + /* For direction periodic, we need the pull vector to be one of the box vectors + (or more generally I guess it could be an integer combination of boxvectors). + This boxvector should to be orthogonal to the (periodic) plane spanned by the other two box vectors. + Here we assume that the pull vector is either x, y or z. + * E.g. for pull vec = (1, 0, 0) the box vector tensor should look like: + * | x 0 0 | + * | 0 a c | + * | 0 b d | + * + The period is then given by the box length x. + + Note: we make these checks here for AWH and not in pull because we allow pull to be more general. + */ + int m_pullvec, count_nonzeros = 0; + + /* Check that pull vec has only one component and which component it is. This component gives the relevant box vector */ + for (int m = 0; m < DIM; m++) + { + if (pcrd_params->vec[m] != 0) + { + m_pullvec = m; + count_nonzeros++; + } + } + if (count_nonzeros != 1) + { + gmx_fatal(FARGS, "For AWH biasing pull coordinate %d with pull geometry %s, the pull vector needs to be parallel to " + "a box vector that is parallel to either the x, y or z axis and is orthogonal to the other box vectors.", + coord_ind + 1, EPULLGEOM(epullgDIRPBC)); + } + + /* Check that there is a box vec parallel to pull vec and that this boxvec is orthogonal to the other box vectors */ + for (int m = 0; m < DIM; m++) + { + for (int n = 0; n < DIM; n++) + { + if ((n != m) && (n == m_pullvec || m == m_pullvec) && box[m][n] > 0) + { + gmx_fatal(FARGS, "For AWH biasing pull coordinate %d with pull geometry %s, there needs to be a box vector parallel to the pull vector that is " + "orthogonal to the other box vectors.", + coord_ind + 1, EPULLGEOM(epullgDIRPBC)); + } + } + } + + /* If this box vector only has one component as we assumed the norm should be equal to the absolute value of that component */ + period = static_cast(norm(box[m_pullvec])); + } + else if (pcrd_params->eGeom == epullgDIHEDRAL) + { + /* The dihedral angle is periodic in -180 to 180 deg */ + period = 360; + } + else + { + period = 0; + } + + return period; +} + +/*! \brief + * Checks if the given interval is defined in the correct periodic interval. + * + * \param[in] origin Start value of interval. + * \param[in] end End value of interval. + * \param[in] period Period (or 0 if not periodic). + * \returns true if the end point values are in the correct periodic interval. + */ +static bool intervalIsInPeriodicInterval(double origin, double end, double period) +{ + return (period == 0) || (std::fabs(origin) <= 0.5*period && std::fabs(end) <= 0.5*period); +} + +/*! \brief + * Checks if a value is within an interval. + * + * \param[in] origin Start value of interval. + * \param[in] end End value of interval. + * \param[in] period Period (or 0 if not periodic). + * \param[in] value Value to check. + * \returns true if the value is within the interval. + */ +static bool valueIsInInterval(double origin, double end, double period, double value) +{ + bool bIn_interval; + + if (period > 0) + { + if (origin < end) + { + /* The interval closes within the periodic interval */ + bIn_interval = (value >= origin) && (value <= end); + } + else + { + /* The interval wraps around the periodic boundary */ + bIn_interval = ((value >= origin) && (value <= 0.5*period)) || ((value >= -0.5*period) && (value <= end)); + } + } + else + { + bIn_interval = (value >= origin) && (value <= end); + } + + return bIn_interval; +} + +/*! \brief + * Check if the starting configuration is consistent with the given interval. + * + * \param[in] awhParams AWH parameters. + * \param[in,out] wi Struct for bookeeping warnings. + */ +static void checkInputConsistencyInterval(const AwhParams *awhParams, warninp_t wi) +{ + for (int k = 0; k < awhParams->numBias; k++) + { + AwhBiasParams *awhBiasParams = &awhParams->awhBiasParams[k]; + for (int d = 0; d < awhBiasParams->ndim; d++) + { + AwhDimParams *dimParams = &awhBiasParams->dimParams[d]; + int coordIndex = dimParams->coordIndex; + double origin = dimParams->origin, end = dimParams->end, period = dimParams->period; + double coordValueInit = dimParams->coordValueInit; + + if ((period == 0) && (origin > end)) + { + gmx_fatal(FARGS, "For the non-periodic pull coordinates awh%d-dim%d-start cannot be larger than awh%d-dim%d-end", + k + 1, d + 1, origin, k + 1, d + 1, end); + } + + /* Currently we assume symmetric periodic intervals, meaning we use [-period/2, period/2] as the reference interval. + Make sure the AWH interval is within this reference interval. + + Note: we could fairly simply allow using a more general interval (e.g. [x, x + period]) but it complicates + things slightly and I don't see that there is a great need for it. It would also mean that the interval would + depend on AWH input. Also, for dihedral angles you would always want the reference interval to be -180, +180, + independent of AWH parameters. + */ + if (!intervalIsInPeriodicInterval(origin, end, period)) + { + gmx_fatal(FARGS, "When using AWH with periodic pull coordinate geometries awh%d-dim%d-start (%.8g) and " + "awh%d-dim%d-end (%.8g) should cover at most one period (%.8g) and take values in between " + "minus half a period and plus half a period, i.e. in the interval [%.8g, %.8g].", + k + 1, d + 1, origin, k + 1, d + 1, end, + period, -0.5*period, 0.5*period); + + } + + /* Warn if the pull initial coordinate value is not in the grid */ + if (!valueIsInInterval(origin, end, period, coordValueInit)) + { + char warningmsg[STRLEN]; + sprintf(warningmsg, "The initial coordinate value (%.8g) for pull coordinate index %d falls outside " + "of the sampling nterval awh%d-dim%d-start (%.8g) to awh%d-dim%d-end (%.8g). " + "This can lead to large initial forces pulling the coordinate towards the sampling interval.", + coordValueInit, coordIndex + 1, + k + 1, d + 1, origin, k + 1, d + 1, end); + warning(wi, warningmsg); + } + } + } +} + +void setStateDependentAwhParams(AwhParams *awhParams, + const pull_params_t *pull_params, pull_t *pull_work, + const matrix box, int ePBC, + const t_grpopts *inputrecGroupOptions, warninp_t wi) +{ + /* The temperature is not really state depenendent but is not known + * when read_awhParams is called (in get ir). + * It is known first after do_index has been called in grompp.cpp. + */ + if (inputrecGroupOptions->ref_t == NULL || + inputrecGroupOptions->ref_t[0] <= 0) + { + gmx_fatal(FARGS, "AWH biasing is only supported for temperatures > 0"); + } + for (int i = 1; i < inputrecGroupOptions->ngtc; i++) + { + if (inputrecGroupOptions->ref_t[i] != inputrecGroupOptions->ref_t[0]) + { + gmx_fatal(FARGS, "AWH biasing is currently only supported for identical temperatures for all temperature coupling groups"); + } + } + + t_pbc pbc; + set_pbc(&pbc, ePBC, box); + + for (int k = 0; k < awhParams->numBias; k++) + { + AwhBiasParams *awhBiasParams = &awhParams->awhBiasParams[k]; + for (int d = 0; d < awhBiasParams->ndim; d++) + { + AwhDimParams *dimParams = &awhBiasParams->dimParams[d]; + + /* The periodiciy of the AWH grid in certain cases depends on the simulation box */ + dimParams->period = get_pull_coord_period(pull_params, dimParams->coordIndex, box); + + /* The initial coordinate value, converted to external user units. */ + dimParams->coordValueInit = + get_pull_coord_value(pull_work, dimParams->coordIndex, &pbc); + + t_pull_coord *pullCoord = &pull_params->coord[dimParams->coordIndex]; + dimParams->coordValueInit *= pull_conversion_factor_internal2userinput(pullCoord); + } + } + checkInputConsistencyInterval(awhParams, wi); + + /* Register AWH as external potential with pull to check consistency. */ + Awh::registerAwhWithPull(*awhParams, pull_work); +} + +} // namespace gmx diff --git a/src/gromacs/awh/read-params.h b/src/gromacs/awh/read-params.h new file mode 100644 index 0000000000..f22fad92b8 --- /dev/null +++ b/src/gromacs/awh/read-params.h @@ -0,0 +1,98 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \libinternal \file + * + * \brief + * Declares functions needed for reading, initializing and setting the AWH parameter data types. + * + * \author Viveca Lindahl + * \inlibraryapi + * \ingroup module_awh + */ + +#ifndef GMX_AWH_READPARAMS_H +#define GMX_AWH_READPARAMS_H + +#include "gromacs/fileio/readinp.h" +#include "gromacs/math/vectypes.h" + +struct t_grpopts; +struct t_inputrec; +struct pull_params_t; +struct pull_t; + +namespace gmx +{ +struct AwhParams; + +/*! \brief Allocate, initialize and check the AWH parameters with values from the input file. + * + * \param[in,out] ninp_p Number of read input file entries. + * \param[in,out] inp_p Input file entries. + * \param[in] inputrec Input parameter struct. + * \param[in,out] wi Struct for bookeeping warnings. + * \returns AWH parameters. + */ +AwhParams *readAndCheckAwhParams(int *ninp_p, + t_inpfile **inp_p, + const t_inputrec *inputrec, + warninp_t wi); + + +/*! \brief + * Sets AWH parameters that need state parameters such as the box vectors. + * + * \param[in,out] awhParams AWH parameters. + * \param[in] pull_params Pull parameters. + * \param[in,out] pull_work Pull working struct to register AWH bias in. + * \param[in] box Box vectors. + * \param[in] ePBC Periodic boundary conditions enum. + * \param[in] inputrecGroupOptions Parameters for atom groups. + * \param[in,out] wi Struct for bookeeping warnings. + * + * \note This function currently relies on the function set_pull_init to have been called. + */ +void setStateDependentAwhParams(AwhParams *awhParams, + const pull_params_t *pull_params, + pull_t *pull_work, + const matrix box, + int ePBC, + const t_grpopts *inputrecGroupOptions, + warninp_t wi); + +} // namespace gmx + +#endif /* GMX_AWH_READPARAMS_H */ diff --git a/src/gromacs/awh/tests/CMakeLists.txt b/src/gromacs/awh/tests/CMakeLists.txt new file mode 100644 index 0000000000..691be579a4 --- /dev/null +++ b/src/gromacs/awh/tests/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# This file is part of the GROMACS molecular simulation package. +# +# Copyright (c) 2017, by the GROMACS development team, led by +# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, +# and including many others, as listed in the AUTHORS file in the +# top-level source directory and at http://www.gromacs.org. +# +# GROMACS is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# GROMACS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with GROMACS; if not, see +# http://www.gnu.org/licenses, or write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# If you want to redistribute modifications to GROMACS, please +# consider that scientific software is very special. Version +# control is crucial - bugs must be traceable. We will be happy to +# consider code for inclusion in the official distribution, but +# derived work must not be called official GROMACS. Details are found +# in the README & COPYING files - if they are missing, get the +# official version at http://www.gromacs.org. +# +# To help us fund GROMACS development, we humbly ask that you cite +# the research papers on the package. Check out http://www.gromacs.org. + +gmx_add_unit_test(AwhTest awh-test + bias.cpp biasstate.cpp grid.cpp) diff --git a/src/gromacs/awh/tests/bias.cpp b/src/gromacs/awh/tests/bias.cpp new file mode 100644 index 0000000000..37961abd70 --- /dev/null +++ b/src/gromacs/awh/tests/bias.cpp @@ -0,0 +1,341 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +#include "gmxpre.h" + +#include "gromacs/awh/bias.h" + +#include + +#include +#include +#include + +#include +#include + +#include "gromacs/awh/pointstate.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/stringutil.h" + +#include "testutils/refdata.h" +#include "testutils/testasserts.h" + +namespace gmx +{ + +namespace test +{ + +/*! \internal \brief + * Struct that gathers all input for setting up and using a Bias + */ +struct AwhTestParameters +{ + double beta; //!< 1/(kB*T) + + AwhDimParams awhDimParams; //!< Dimension parameters pointed to by \p awhBiasParams + AwhBiasParams awhBiasParams; //!< Bias parameters pointed to by \[ awhParams + AwhParams awhParams; //!< AWH parameters, this is the struct to actually use + + std::vector dimParams; //!< Dimension parameters for setting up Bias +}; + +//! Helper function to set up the C-style AWH parameters for the test +static AwhTestParameters getAwhTestParameters(int eawhgrowth, + int eawhpotential) +{ + AwhTestParameters params; + + params.beta = 0.4; + + AwhDimParams &awhDimParams = params.awhDimParams; + + awhDimParams.period = 0; + awhDimParams.diffusion = 0.1; + awhDimParams.origin = 0.5; + awhDimParams.end = 1.5; + awhDimParams.coordValueInit = awhDimParams.origin; + awhDimParams.coverDiameter = 0; + + AwhBiasParams &awhBiasParams = params.awhBiasParams; + + awhBiasParams.ndim = 1; + awhBiasParams.dimParams = &awhDimParams; + awhBiasParams.eTarget = eawhtargetCONSTANT; + awhBiasParams.targetBetaScaling = 0; + awhBiasParams.targetCutoff = 0; + awhBiasParams.eGrowth = eawhgrowth; + awhBiasParams.bUserData = FALSE; + awhBiasParams.errorInitial = 0.5/params.beta; + awhBiasParams.shareGroup = 0; + awhBiasParams.equilibrateHistogram = FALSE; + + double convFactor = 1; + double k = 1000; + gmx_int64_t seed = 93471803; + + params.dimParams.push_back(DimParams(convFactor, k, params.beta)); + + AwhParams &awhParams = params.awhParams; + + awhParams.numBias = 1; + awhParams.awhBiasParams = &awhBiasParams; + awhParams.seed = seed; + awhParams.nstOut = 0; + awhParams.nstSampleCoord = 1; + awhParams.numSamplesUpdateFreeEnergy = 10; + awhParams.ePotential = eawhpotential; + awhParams.shareBiasMultisim = FALSE; + + return params; +} + +//! Database of 21 test coordinates that represent a trajectory */ +const double g_coords[] = { + 0.62, + 0.70, + 0.68, + 0.80, + 0.93, + 0.87, + 1.16, + 1.14, + 0.95, + 0.89, + 0.91, + 0.86, + 0.88, + 0.79, + 0.75, + 0.82, + 0.74, + 0.70, + 0.68, + 0.71, + 0.73 +}; + +//! Convenience typedef: growth type enum, potential type enum, disable update skips +typedef std::tuple BiasTestParameters; + +/*! \brief Test fixture for testing Bias updates + */ +class BiasTest : public ::testing::TestWithParam +{ + public: + //! Random seed for AWH MC sampling + gmx_int64_t seed_; + + //! Coordinates representing a trajectory in time + std::vector coordinates_; + //! The awh Bias + std::unique_ptr bias_; + + //! Constructor + BiasTest() : + coordinates_(std::begin(g_coords), std::end(g_coords)) + { + /* We test all combinations of: + * eawhgrowth: + * eawhgrowthLINEAR: final, normal update phase + * ewahgrowthEXP_LINEAR: intial phase, updated size is constant + * eawhpotential (should only affect the force output): + * eawhpotentialUMBRELLA: MC on lambda (umbrella potential location) + * eawhpotentialCONVOLVED: MD on a convolved potential landscape + * disableUpdateSkips (should not affect the results): + * BiasParams::DisableUpdateSkips::yes: update the point state for every sample + * BiasParams::DisableUpdateSkips::no: update the point state at an interval > 1 sample + * + * Note: It would be nice to explicitly check that eawhpotential + * and disableUpdateSkips do not affect the point state. + * But the reference data will also ensure this. + */ + int eawhgrowth; + int eawhpotential; + BiasParams::DisableUpdateSkips disableUpdateSkips; + std::tie(eawhgrowth, eawhpotential, disableUpdateSkips) = GetParam(); + + /* Set up a basic AWH setup with a single, 1D bias with parameters + * such that we can measure the effects of different parameters. + * The idea is to, among other things, have part of the interval + * not covered by samples. + */ + const AwhTestParameters params = getAwhTestParameters(eawhgrowth, eawhpotential); + + seed_ = params.awhParams.seed; + + double mdTimeStep = 0.1; + + int numSamples = coordinates_.size() - 1; // No sample taken at step 0 + GMX_RELEASE_ASSERT(numSamples % params.awhParams.numSamplesUpdateFreeEnergy == 0, "This test is intended to reproduce the situation when the might need to write output during a normal AWH run, therefore the number of samples should be a multiple of the free-energy update interval (but the test should also runs fine without this condition)."); + + bias_ = std::unique_ptr(new Bias(-1, params.awhParams, params.awhBiasParams, params.dimParams, params.beta, mdTimeStep, 1, "", Bias::ThisRankWillDoIO::No, disableUpdateSkips)); + } +}; + +TEST_P(BiasTest, ForcesBiasPmf) +{ + gmx::test::TestReferenceData data; + gmx::test::TestReferenceChecker checker(data.rootChecker()); + + Bias &bias = *bias_.get(); + + /* Make strings with the properties we expect to be different in the tests. + * These also helps to interpret the reference data. + */ + std::vector props; + props.push_back(formatString("stage: %s", bias.state().inInitialStage() ? "initial" : "final")); + props.push_back(formatString("convolve forces: %s", bias.params().convolveForce ? "yes" : "no")); + props.push_back(formatString("skip updates: %s", bias.params().skipUpdates() ? "yes" : "no")); + + SCOPED_TRACE(gmx::formatString("%s, %s, %s", props[0].c_str(), props[1].c_str(), props[2].c_str())); + + std::vector force, pot, potJump; + + double coordMaxValue = 0; + double potentialJump = 0; + gmx_int64_t step = 0; + for (auto &coord : coordinates_) + { + coordMaxValue = std::max(coordMaxValue, std::abs(coord)); + + awh_dvec coordValue = { coord, 0, 0, 0 }; + awh_dvec biasForce; + double potential = 0; + bias.calcForceAndUpdateBias(coordValue, + biasForce, &potential, &potentialJump, + nullptr, step, step, seed_, nullptr); + + force.push_back(biasForce[0]); + pot.push_back(potential); + potJump.push_back(potentialJump); + + step++; + } + + /* When skipping updates, ensure all skipped updates are performed here. + * This should result in the same bias state as at output in a normal run. + */ + if (bias.params().skipUpdates()) + { + bias.doSkippedUpdatesForAllPoints(); + } + + std::vector pointBias, logPmfsum; + for (auto &point : bias.state().points()) + { + pointBias.push_back(point.bias()); + logPmfsum.push_back(point.logPmfSum()); + } + + /* The umbrella force is computed from the coordinate deviation. + * In taking this deviation we lose a lot of precision, so we should + * compare against k*max(coord) instead of the instantaneous force. + */ + const double kCoordMax = bias.dimParams()[0].k*coordMaxValue; + + const double ulpTol = 10; + + checker.checkSequence(props.begin(), props.end(), "Properties"); + checker.setDefaultTolerance(absoluteTolerance(kCoordMax*GMX_DOUBLE_EPS*ulpTol)); + checker.checkSequence(force.begin(), force.end(), "Force"); + checker.checkSequence(pot.begin(), pot.end(), "Potential"); + checker.checkSequence(potJump.begin(), potJump.end(), "PotentialJump"); + checker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpTol)); + checker.checkSequence(pointBias.begin(), pointBias.end(), "PointBias"); + checker.checkSequence(logPmfsum.begin(), logPmfsum.end(), "PointLogPmfsum"); +} + +/* Scan initial/final phase, MC/convolved force and update skip (not) allowed + * Both the convolving and skipping should not affect the bias and PMF. + * It would be nice if the test would explicitly check for this. + * Currently this is tested through identical reference data. + */ +INSTANTIATE_TEST_CASE_P(WithParameters, BiasTest, + ::testing::Combine( + ::testing::Values(eawhgrowthLINEAR, eawhgrowthEXP_LINEAR), + ::testing::Values(eawhpotentialUMBRELLA, eawhpotentialCONVOLVED), + ::testing::Values(BiasParams::DisableUpdateSkips::yes, BiasParams::DisableUpdateSkips::no))); + +// Test that we detect coverings and exit the initial stage at the correct step +TEST(BiasTest, DetectsCovering) +{ + const AwhTestParameters params = getAwhTestParameters(eawhgrowthEXP_LINEAR, eawhpotentialCONVOLVED); + const AwhDimParams &awhDimParams = params.awhParams.awhBiasParams[0].dimParams[0]; + + const double mdTimeStep = 0.1; + + Bias bias(-1, params.awhParams, params.awhBiasParams, params.dimParams, params.beta, mdTimeStep, 1, "", Bias::ThisRankWillDoIO::No); + + /* We use a trajectory of the sum of two sines to cover the reaction + * coordinate range in a semi-realistic way. The period is 4*pi=12.57. + * We get out of the initial stage after 4 coverings at step 300. + */ + const gmx_int64_t exitStepRef = 300; + const double midPoint = 0.5*(awhDimParams.end + awhDimParams.origin); + const double halfWidth = 0.5*(awhDimParams.end - awhDimParams.origin); + + bool inInitialStage = bias.state().inInitialStage(); + /* Normally this loop exits at exitStepRef, but we extend with failure */ + gmx_int64_t step; + for (step = 0; step <= 2*exitStepRef; step++) + { + double t = step*mdTimeStep; + double coord = midPoint + halfWidth*(0.5*std::sin(t) + 0.55*std::sin(1.5*t)); + + awh_dvec coordValue = { coord, 0, 0, 0 }; + awh_dvec biasForce; + double potential = 0; + double potentialJump = 0; + bias.calcForceAndUpdateBias(coordValue, + biasForce, &potential, &potentialJump, + nullptr, step, step, params.awhParams.seed, nullptr); + + inInitialStage = bias.state().inInitialStage(); + if (!inInitialStage) + { + break; + } + } + + EXPECT_EQ(false, inInitialStage); + if (!inInitialStage) + { + EXPECT_EQ(exitStepRef, step); + } +} + +} // namespace +} // namespace diff --git a/src/gromacs/awh/tests/biasstate.cpp b/src/gromacs/awh/tests/biasstate.cpp new file mode 100644 index 0000000000..6fb6b4429e --- /dev/null +++ b/src/gromacs/awh/tests/biasstate.cpp @@ -0,0 +1,184 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +#include "gmxpre.h" + +#include "gromacs/awh/biasstate.h" + +#include + +#include +#include + +#include +#include + +#include "gromacs/awh/grid.h" +#include "gromacs/awh/pointstate.h" +#include "gromacs/math/functions.h" +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/smalloc.h" + +#include "testutils/testasserts.h" +#include "testutils/testfilemanager.h" + +namespace gmx +{ + +namespace test +{ + +/*! \internal \brief + * Struct that gathers all input for setting up and using a Bias + */ +struct AwhTestParameters +{ + double beta; //!< 1/(kB*T) + + AwhDimParams awhDimParams[2]; //!< Dimension parameters pointed to by \p awhBiasParams + AwhBiasParams awhBiasParams; //!< Bias parameters pointed to by \[ awhParams + AwhParams awhParams; //!< AWH parameters, this is the struct to actually use +}; + +//! Helper function to set up the C-style AWH parameters for the test +static AwhTestParameters getAwhTestParameters() +{ + AwhTestParameters params; + + params.beta = 1.0; + + AwhParams &awhParams = params.awhParams; + snew(params.awhParams.awhBiasParams, 1); + AwhBiasParams &awhBiasParams = params.awhParams.awhBiasParams[0]; + snew(awhBiasParams.dimParams, 2); + + AwhDimParams &awhDimParams0 = awhBiasParams.dimParams[0]; + + awhDimParams0.period = 0; + awhDimParams0.diffusion = 0.1; + awhDimParams0.origin = 0.5; + awhDimParams0.end = 1.5; + awhDimParams0.coordValueInit = awhDimParams0.origin; + awhDimParams0.coverDiameter = 0; + + AwhDimParams &awhDimParams1 = awhBiasParams.dimParams[1]; + + awhDimParams1.period = 0; + awhDimParams1.diffusion = 0.1; + awhDimParams1.origin = 0.8; + awhDimParams1.end = 1.3; + awhDimParams1.coordValueInit = awhDimParams1.origin; + awhDimParams1.coverDiameter = 0; + + awhBiasParams.ndim = 2; + awhBiasParams.eTarget = eawhtargetCONSTANT; + awhBiasParams.targetBetaScaling = 0; + awhBiasParams.targetCutoff = 0; + awhBiasParams.eGrowth = eawhgrowthLINEAR; + awhBiasParams.bUserData = TRUE; + awhBiasParams.errorInitial = 0.5; + awhBiasParams.shareGroup = 0; + awhBiasParams.equilibrateHistogram = FALSE; + + awhParams.numBias = 1; + awhParams.seed = 93471803; + awhParams.nstOut = 0; + awhParams.nstSampleCoord = 1; + awhParams.numSamplesUpdateFreeEnergy = 10; + awhParams.ePotential = eawhpotentialCONVOLVED; + awhParams.shareBiasMultisim = FALSE; + + return params; +} + +/*! \brief Test fixture for testing Bias updates + */ +class BiasStateTest : public ::testing::TestWithParam +{ + public: + std::unique_ptr biasState_; //!< The bias state + + //! Constructor + BiasStateTest() + { + AwhTestParameters params = getAwhTestParameters(); + const AwhParams &awhParams = params.awhParams; + const AwhBiasParams &awhBiasParams = awhParams.awhBiasParams[0]; + std::vector dimParams; + dimParams.push_back(DimParams(1.0, 15.0, params.beta)); + dimParams.push_back(DimParams(1.0, 15.0, params.beta)); + Grid grid(dimParams, awhBiasParams.dimParams); + BiasParams biasParams(awhParams, awhBiasParams, dimParams, 1.0, 1.0, BiasParams::DisableUpdateSkips::no, 1, grid.axis(), 0); + biasState_ = std::unique_ptr(new BiasState(awhBiasParams, 1.0, dimParams, grid)); + + // Here we initialize the grid point state using the input file + std::string filename = gmx::test::TestFileManager::getInputFilePath(GetParam()); + biasState_->initGridPointState(awhBiasParams, dimParams, grid, biasParams, filename, params.awhParams.numBias); + + sfree(params.awhParams.awhBiasParams[0].dimParams); + sfree(params.awhParams.awhBiasParams); + } +}; + +TEST_P(BiasStateTest, InitializesFromFile) +{ + gmx::ArrayRef points = biasState_->points(); + + /* Compute the mean square deviation from the expected values in the file. + * The PMF values are spaced by 0.5 per points and logPmfsum has opposite sign. + * The target is (index + 1)/120. + */ + double msdPmf = 0; + double msdTarget = 0; + for (size_t i = 0; i < points.size(); i++) + { + msdPmf += gmx::square(points[i].logPmfSum() - points[0].logPmfSum() + 0.5*i); + msdTarget += gmx::square(points[i].target() - (i + 1)/120.0); + } + + msdPmf /= points.size(); + msdTarget /= points.size(); + + EXPECT_EQ(0.0, msdPmf); + EXPECT_EQ(0.0, msdTarget); +} + +// Test that Bias initialization open and reads the correct initialization +// files and the the correct PMF and target distribution is set. +INSTANTIATE_TEST_CASE_P(WithParameters, BiasStateTest, + ::testing::Values("pmf_target_format0.xvg", + "pmf_target_format1.xvg")); + +} // namespace +} // namespace diff --git a/src/gromacs/awh/tests/grid.cpp b/src/gromacs/awh/tests/grid.cpp new file mode 100644 index 0000000000..8cc7a87291 --- /dev/null +++ b/src/gromacs/awh/tests/grid.cpp @@ -0,0 +1,185 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +#include "gmxpre.h" + +#include "gromacs/awh/grid.h" + +#include + +#include +#include + +#include +#include + +#include "gromacs/mdtypes/awh-params.h" +#include "gromacs/utility/gmxassert.h" + +#include "testutils/testasserts.h" + +namespace gmx +{ + +namespace test +{ + +TEST(gridTest, neighborhood) +{ + constexpr double pointsPerScope = Grid::c_scopeCutoff*Grid::c_numPointsPerSigma; + GMX_RELEASE_ASSERT(std::abs(pointsPerScope - std::round(pointsPerScope)) > 1e-4, "If the scope is close to an integer number of points, this test can be unstable due to rounding issues"); + + const int scopeInPoints = static_cast(pointsPerScope); + + /* We test a 2-dimensional grid with dim0 periodic, dim1 not periodic. + * This test should cover most relevant cases: multi-dimension grids + * and non-periodic and periodic dimensions. + * The only thing not tested here is a periodic dimension with a gap. + */ + + const int numDim = 2; + std::vector awhDimParams(numDim); + + awhDimParams[0].origin = -5; + awhDimParams[0].end = 5; + awhDimParams[0].period = 10; + + awhDimParams[1].origin = 0.5; + awhDimParams[1].end = 2.0; + awhDimParams[1].period = 0; + + const real conversionFactor = 1; + const real beta = 3.0; + + /* Set up dimParams to get about 15 points along each dimension */ + std::vector dimParams; + dimParams.push_back(DimParams(conversionFactor, 1/(beta*0.7*0.7), beta)); + dimParams.push_back(DimParams(conversionFactor, 1/(beta*0.1*0.1), beta)); + + Grid grid(dimParams, awhDimParams.data()); + + const int numPoints = grid.numPoints(); + + int numPointsDim[numDim]; + for (int d = 0; d < numDim; d++) + { + numPointsDim[d] = grid.axis(d).numPoints(); + + GMX_RELEASE_ASSERT(numPointsDim[d] >= 2*scopeInPoints + 1, "The test code currently assume that the grid is larger of equal to the scope in each dimension"); + } + int halfNumPoints0 = numPointsDim[0]/2; + + bool haveOutOfGridNeighbors = false; + bool haveDuplicateNeighbors = false; + bool haveIncorrectNeighbors = false; + bool haveCorrectNumNeighbors = true; + + /* Set up a grid for checking for duplicate neighbors */ + std::vector isInNeighborhood(grid.numPoints(), false); + + /* Checking for all points is overkill, we check every 7th */ + for (size_t i = 0; i < grid.numPoints(); i += 7) + { + const GridPoint &point = grid.point(i); + + /* NOTE: This code relies on major-minor index ordering in Grid */ + int pointIndex0 = i/numPointsDim[1]; + int pointIndex1 = i - pointIndex0*numPointsDim[1]; + + /* To check if we have the correct neighbors, we check the expected + * number of neighbors, if all neighbors are within the grid bounds + * and if they are within scope. + */ + int distanceFromEdge1 = std::min(pointIndex1, numPointsDim[1] - 1 - pointIndex1); + size_t numNeighbors = (2*scopeInPoints + 1)*(scopeInPoints + std::min(scopeInPoints, distanceFromEdge1) + 1); + if (point.neighbor.size() != numNeighbors) + { + haveCorrectNumNeighbors = false; + } + + for (auto &j : point.neighbor) + { + if (j >= 0 && j < numPoints) + { + if (isInNeighborhood[j]) + { + haveDuplicateNeighbors = true; + } + isInNeighborhood[j] = true; + + int neighborIndex0 = j/numPointsDim[1]; + int neighborIndex1 = j - neighborIndex0*numPointsDim[1]; + + int distance0 = neighborIndex0 - pointIndex0; + int distance1 = neighborIndex1 - pointIndex1; + /* Adjust distance for periodicity of dimension 0 */ + if (distance0 < -halfNumPoints0) + { + distance0 += numPointsDim[0]; + } + else if (distance0 > halfNumPoints0) + { + distance0 -= numPointsDim[0]; + } + /* Check if the distance is within scope */ + if (distance0 < -scopeInPoints || distance0 > scopeInPoints || + distance1 < -scopeInPoints || distance1 > scopeInPoints) + { + haveIncorrectNeighbors = true; + } + } + else + { + haveOutOfGridNeighbors = true; + } + } + + /* Clear the marked points in the checking grid */ + for (auto &neighbor : point.neighbor) + { + if (neighbor >= 0 && neighbor < numPoints) + { + isInNeighborhood[neighbor] = false; + } + } + } + + EXPECT_FALSE(haveOutOfGridNeighbors); + EXPECT_FALSE(haveDuplicateNeighbors); + EXPECT_FALSE(haveIncorrectNeighbors); + EXPECT_TRUE(haveCorrectNumNeighbors); +} + +} // namespace +} // namespace diff --git a/src/gromacs/awh/tests/pmf_target_format0.xvg b/src/gromacs/awh/tests/pmf_target_format0.xvg new file mode 100644 index 0000000000..348e7f60a8 --- /dev/null +++ b/src/gromacs/awh/tests/pmf_target_format0.xvg @@ -0,0 +1,15 @@ +0.5 0.8 0.0 1 +0.5 1.05 0.5 2 +0.5 1.3 1.0 3 +0.75 0.8 1.5 4 +0.75 1.05 2.0 5 +0.75 1.3 2.5 6 +1.0 0.8 3.0 7 +1.0 1.05 3.5 8 +1.0 1.3 4.0 9 +1.25 0.8 4.5 10 +1.25 1.05 5.0 11 +1.25 1.3 5.5 12 +1.5 0.8 6.0 13 +1.5 1.05 6.5 14 +1.5 1.3 7.0 15 diff --git a/src/gromacs/awh/tests/pmf_target_format1.xvg b/src/gromacs/awh/tests/pmf_target_format1.xvg new file mode 100644 index 0000000000..340390abee --- /dev/null +++ b/src/gromacs/awh/tests/pmf_target_format1.xvg @@ -0,0 +1,15 @@ +0.5 0.8 0.0 0 0 0 1 +0.5 1.05 0.5 0 0 0 2 +0.5 1.3 1.0 0 0 0 3 +0.75 0.8 1.5 0 0 0 4 +0.75 1.05 2.0 0 0 0 5 +0.75 1.3 2.5 0 0 0 6 +1.0 0.8 3.0 0 0 0 7 +1.0 1.05 3.5 0 0 0 8 +1.0 1.3 4.0 0 0 0 9 +1.25 0.8 4.5 0 0 0 10 +1.25 1.05 5.0 0 0 0 11 +1.25 1.3 5.5 0 0 0 12 +1.5 0.8 6.0 0 0 0 13 +1.5 1.05 6.5 0 0 0 14 +1.5 1.3 7.0 0 0 0 15 diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_0.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_0.xml new file mode 100644 index 0000000000..953a44d524 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_0.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: final + convolve forces: no + skip updates: no + + + 21 + -70.000000000000014 + -49.999999999999986 + -5.0000000000000604 + -49.999999999999993 + -5 + 80.000000000000014 + -184.99999999999989 + -64.999999999999829 + 75.000000000000071 + 10.000000000000011 + -59.999999999999943 + -9.999999999999897 + -4.9999999999999485 + 60 + 0 + -44.999999999999929 + 60.000000000000057 + 5.3290705182007514e-14 + -30.000000000000028 + -59.999999999999943 + -29.999999999999972 + + + 21 + 7.1999999999999993 + 4.9999999999999973 + 0.19999999999999815 + 11.250000000000004 + 3.1999999999999966 + 8.4500000000000011 + 33.799999999999969 + 4.0499999999999874 + 11.250000000000021 + 1.7999999999999965 + 1.7999999999999965 + 0.049999999999998976 + 0.44999999999999746 + 6.049999999999998 + 1.2500000000000022 + 7.1999999999999993 + 6.0500000000000105 + 1.2500000000000022 + 0.45000000000000084 + 1.7999999999999965 + 3.1999999999999966 + + + 21 + -6.9999999999999991 + -4.9999999999999973 + 0.25000000000000266 + -10.000000000000002 + -0.75 + -8 + -27.749999999999982 + -3.2499999999999947 + -11.250000000000021 + -0.99999999999999956 + 0 + 0 + -0.24999999999999711 + -5.9999999999999982 + 0 + -6.7499999999999956 + -6.0000000000000107 + -5.5511151231257827e-15 + 0 + 0 + -2.9999999999999964 + + + 21 + -2.7472944451046044 + -2.761382468216012 + -2.8841487700294044 + -3.2209538871703414 + -3.5026939887681463 + -3.5195714843782056 + -3.4502423109495881 + -3.469814270443778 + -3.4405375988254123 + -3.2226649751609364 + -2.9351106219650442 + -2.8182713948302314 + -2.9109204815675662 + -2.9974201933988711 + -2.909045620263937 + -2.7875440262964757 + -2.7504540277720304 + -2.7468371894369819 + -2.7467110222070819 + -2.7467093840049168 + -2.7467093840049168 + + + 21 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.6151742683824608 + 4.4405502576634461 + 4.4441023756206013 + 4.3362531480424424 + 4.4431072875080853 + 4.3095557059880116 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.3095557021915543 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_1.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_1.xml new file mode 100644 index 0000000000..0e77b34d81 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_1.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: final + convolve forces: no + skip updates: yes + + + 21 + -70.000000000000014 + -49.999999999999986 + -5.0000000000000604 + -49.999999999999993 + -5 + 80.000000000000014 + -184.99999999999989 + -64.999999999999829 + 75.000000000000071 + 10.000000000000011 + -59.999999999999943 + -9.999999999999897 + -4.9999999999999485 + 60 + 0 + -44.999999999999929 + 60.000000000000057 + 5.3290705182007514e-14 + -30.000000000000028 + -59.999999999999943 + -29.999999999999972 + + + 21 + 7.1999999999999993 + 4.9999999999999973 + 0.19999999999999815 + 11.250000000000004 + 3.1999999999999966 + 8.4500000000000011 + 33.799999999999969 + 4.0499999999999874 + 11.250000000000021 + 1.7999999999999965 + 1.7999999999999965 + 0.049999999999998976 + 0.44999999999999746 + 6.049999999999998 + 1.2500000000000022 + 7.1999999999999993 + 6.0500000000000105 + 1.2500000000000022 + 0.45000000000000084 + 1.7999999999999965 + 3.1999999999999966 + + + 21 + -6.9999999999999991 + -4.9999999999999973 + 0.25000000000000266 + -10.000000000000002 + -0.75 + -8 + -27.749999999999982 + -3.2499999999999947 + -11.250000000000021 + -0.99999999999999956 + 0 + 0 + -0.24999999999999711 + -5.9999999999999982 + 0 + -6.7499999999999956 + -6.0000000000000107 + -5.5511151231257827e-15 + 0 + 0 + -2.9999999999999964 + + + 21 + -2.7472944451046044 + -2.761382468216012 + -2.8841487700294044 + -3.2209538871703414 + -3.5026939887681463 + -3.5195714843782056 + -3.4502423109495881 + -3.469814270443778 + -3.4405375988254123 + -3.2226649751609364 + -2.9351106219650442 + -2.8182713948302314 + -2.9109204815675662 + -2.9974201933988711 + -2.909045620263937 + -2.7875440262964757 + -2.7504540277720304 + -2.7468371894369819 + -2.7467110222070819 + -2.7467093840049168 + -2.7467093840049168 + + + 21 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.6151742683824608 + 4.4405502576634461 + 4.4441023756206013 + 4.3362531480424424 + 4.4431072875080853 + 4.3095557059880116 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.3095557021915543 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_2.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_2.xml new file mode 100644 index 0000000000..533383afa7 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_2.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: final + convolve forces: yes + skip updates: no + + + 21 + 0.21526724902654029 + 0.00037168043378189554 + 0.0023506021278633634 + 1.8322908294787776e-14 + 1.8182159860257569e-05 + -1.8182159861071263e-05 + -6.7883759693111072e-06 + 6.7883758926473613e-06 + -4.5547116425681544e-14 + 6.7883759521357456e-06 + -6.7883759527878661e-06 + -3.5538537690811558 + -2.0147107048387825 + -2.7052521678814725 + -1.0406813794780787 + -3.967066260592369 + -0.87907598448922164 + -1.6612940249811217 + -2.6184399315060456 + -1.2743003165852991 + -0.86472575028406273 + + + 21 + 5.3171052747656216 + 5.3139634951471235 + 5.3139852805648378 + 5.3139597783455503 + 5.313959928304433 + 5.313959928304433 + 5.3139598111771384 + 5.3139598111771384 + 5.3139597783455486 + 5.3139598111771358 + 5.3139598111771358 + 5.8523574407739236 + 5.909517708141518 + 5.5891055083437386 + 5.5179043538034485 + 5.6909400832286146 + 5.5084236091309347 + 5.4647638463620405 + 5.4221743541770948 + 5.4793615522962513 + 5.4998305387193387 + + + 21 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.60502446654887354 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.83485389150670564 + + + 21 + -2.7472944451046044 + -2.761382468216012 + -2.8841487700294044 + -3.2209538871703414 + -3.5026939887681463 + -3.5195714843782056 + -3.4502423109495881 + -3.469814270443778 + -3.4405375988254123 + -3.2226649751609364 + -2.9351106219650442 + -2.8182713948302314 + -2.9109204815675662 + -2.9974201933988711 + -2.909045620263937 + -2.7875440262964757 + -2.7504540277720304 + -2.7468371894369819 + -2.7467110222070819 + -2.7467093840049168 + -2.7467093840049168 + + + 21 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.6151742683824608 + 4.4405502576634461 + 4.4441023756206013 + 4.3362531480424424 + 4.4431072875080853 + 4.3095557059880116 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.3095557021915543 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_3.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_3.xml new file mode 100644 index 0000000000..dc189ee266 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_3.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: final + convolve forces: yes + skip updates: yes + + + 21 + 0.21526724902654029 + 0.00037168043378189554 + 0.0023506021278633634 + 1.8322908294787776e-14 + 1.8182159860257569e-05 + -1.8182159861071263e-05 + -6.7883759693111072e-06 + 6.7883758926473613e-06 + -4.5547116425681544e-14 + 6.7883759521357456e-06 + -6.7883759527878661e-06 + -3.5538537690811558 + -2.0147107048387825 + -2.7052521678814725 + -1.0406813794780787 + -3.967066260592369 + -0.87907598448922164 + -1.6612940249811217 + -2.6184399315060456 + -1.2743003165852991 + -0.86472575028406273 + + + 21 + 5.3171052747656216 + 5.3139634951471235 + 5.3139852805648378 + 5.3139597783455503 + 5.313959928304433 + 5.313959928304433 + 5.3139598111771384 + 5.3139598111771384 + 5.3139597783455486 + 5.3139598111771358 + 5.3139598111771358 + 5.8523574407739236 + 5.909517708141518 + 5.5891055083437386 + 5.5179043538034485 + 5.6909400832286146 + 5.5084236091309347 + 5.4647638463620405 + 5.4221743541770948 + 5.4793615522962513 + 5.4998305387193387 + + + 21 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.60502446654887354 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.83485389150670564 + + + 21 + -2.7472944451046044 + -2.761382468216012 + -2.8841487700294044 + -3.2209538871703414 + -3.5026939887681463 + -3.5195714843782056 + -3.4502423109495881 + -3.469814270443778 + -3.4405375988254123 + -3.2226649751609364 + -2.9351106219650442 + -2.8182713948302314 + -2.9109204815675662 + -2.9974201933988711 + -2.909045620263937 + -2.7875440262964757 + -2.7504540277720304 + -2.7468371894369819 + -2.7467110222070819 + -2.7467093840049168 + -2.7467093840049168 + + + 21 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.6151742683824608 + 4.4405502576634461 + 4.4441023756206013 + 4.3362531480424424 + 4.4431072875080853 + 4.3095557059880116 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.3095557021915543 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + 4.0544222744639065 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_4.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_4.xml new file mode 100644 index 0000000000..c57f987772 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_4.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: initial + convolve forces: no + skip updates: no + + + 21 + -70.000000000000014 + -49.999999999999986 + -5.0000000000000604 + -49.999999999999993 + -5 + 80.000000000000014 + -184.99999999999989 + -64.999999999999829 + 75.000000000000071 + 10.000000000000011 + -59.999999999999943 + -9.999999999999897 + -4.9999999999999485 + 60 + 0 + -44.999999999999929 + 60.000000000000057 + 5.3290705182007514e-14 + -30.000000000000028 + -59.999999999999943 + -29.999999999999972 + + + 21 + 7.1999999999999993 + 4.9999999999999973 + 0.19999999999999815 + 11.250000000000004 + 3.1999999999999966 + 8.4500000000000011 + 33.799999999999969 + 4.0499999999999874 + 11.250000000000021 + 1.7999999999999965 + 1.7999999999999965 + 0.049999999999998976 + 0.44999999999999746 + 6.049999999999998 + 1.2500000000000022 + 7.1999999999999993 + 6.0500000000000105 + 1.2500000000000022 + 0.45000000000000084 + 1.7999999999999965 + 3.1999999999999966 + + + 21 + -6.9999999999999991 + -4.9999999999999973 + 0.25000000000000266 + -10.000000000000002 + -0.75 + -8 + -27.749999999999982 + -3.2499999999999947 + -11.250000000000021 + -0.99999999999999956 + 0 + 0 + -0.24999999999999711 + -5.9999999999999982 + 0 + -6.7499999999999956 + -6.0000000000000107 + -5.5511151231257827e-15 + 0 + 0 + -2.9999999999999964 + + + 21 + -2.7252571125147735 + -2.7406945936944584 + -2.8751558537650319 + -3.2406864635799923 + -3.5460149559446359 + -3.5687884755542472 + -3.4901303714495153 + -3.4970015262603624 + -3.4496621504416853 + -3.2125376886451624 + -2.91505863813991 + -2.7962936950351538 + -2.8888307484050659 + -2.9753285114361296 + -2.8869539267116591 + -2.7654523327441973 + -2.7283623342197525 + -2.7247454958847039 + -2.7246193286548039 + -2.7246176904526385 + -2.7246176904526385 + + + 21 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 4.3399332371624695 + 4.1747174038020365 + 4.1623670231442622 + 4.0396595028897746 + 4.1446557041188061 + 3.9896509587172271 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.9896509549207697 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_5.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_5.xml new file mode 100644 index 0000000000..7309c59486 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_5.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: initial + convolve forces: no + skip updates: yes + + + 21 + -70.000000000000014 + -49.999999999999986 + -5.0000000000000604 + -49.999999999999993 + -5 + 80.000000000000014 + -184.99999999999989 + -64.999999999999829 + 75.000000000000071 + 10.000000000000011 + -59.999999999999943 + -9.999999999999897 + -4.9999999999999485 + 60 + 0 + -44.999999999999929 + 60.000000000000057 + 5.3290705182007514e-14 + -30.000000000000028 + -59.999999999999943 + -29.999999999999972 + + + 21 + 7.1999999999999993 + 4.9999999999999973 + 0.19999999999999815 + 11.250000000000004 + 3.1999999999999966 + 8.4500000000000011 + 33.799999999999969 + 4.0499999999999874 + 11.250000000000021 + 1.7999999999999965 + 1.7999999999999965 + 0.049999999999998976 + 0.44999999999999746 + 6.049999999999998 + 1.2500000000000022 + 7.1999999999999993 + 6.0500000000000105 + 1.2500000000000022 + 0.45000000000000084 + 1.7999999999999965 + 3.1999999999999966 + + + 21 + -6.9999999999999991 + -4.9999999999999973 + 0.25000000000000266 + -10.000000000000002 + -0.75 + -8 + -27.749999999999982 + -3.2499999999999947 + -11.250000000000021 + -0.99999999999999956 + 0 + 0 + -0.24999999999999711 + -5.9999999999999982 + 0 + -6.7499999999999956 + -6.0000000000000107 + -5.5511151231257827e-15 + 0 + 0 + -2.9999999999999964 + + + 21 + -2.7252571125147735 + -2.7406945936944584 + -2.8751558537650319 + -3.2406864635799923 + -3.5460149559446359 + -3.5687884755542472 + -3.4901303714495153 + -3.4970015262603624 + -3.4496621504416853 + -3.2125376886451624 + -2.91505863813991 + -2.7962936950351538 + -2.8888307484050659 + -2.9753285114361296 + -2.8869539267116591 + -2.7654523327441973 + -2.7283623342197525 + -2.7247454958847039 + -2.7246193286548039 + -2.7246176904526385 + -2.7246176904526385 + + + 21 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 4.3399332371624695 + 4.1747174038020365 + 4.1623670231442622 + 4.0396595028897746 + 4.1446557041188061 + 3.9896509587172271 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.9896509549207697 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_6.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_6.xml new file mode 100644 index 0000000000..9ede0c3dcb --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_6.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: initial + convolve forces: yes + skip updates: no + + + 21 + 0.21526724902654029 + 0.00037168043378189554 + 0.0023506021278633634 + 1.8322908294787776e-14 + 1.8182159860257569e-05 + -1.8182159861071263e-05 + -6.7883759693111072e-06 + 6.7883758926473613e-06 + -4.5547116425681544e-14 + 6.7883759521357456e-06 + -6.7883759527878661e-06 + -3.5538537690811558 + -2.0147107048387825 + -2.7052521678814725 + -1.0406813794780787 + -3.967066260592369 + -0.87907598448922164 + -1.6612940249811217 + -2.6184399315060456 + -1.2743003165852991 + -0.86472575028406273 + + + 21 + 5.3171052747656216 + 5.3139634951471235 + 5.3139852805648378 + 5.3139597783455503 + 5.313959928304433 + 5.313959928304433 + 5.3139598111771384 + 5.3139598111771384 + 5.3139597783455486 + 5.3139598111771358 + 5.3139598111771358 + 5.8523574407739236 + 5.909517708141518 + 5.5891055083437386 + 5.5179043538034485 + 5.6909400832286146 + 5.5084236091309347 + 5.4647638463620405 + 5.4221743541770948 + 5.4793615522962513 + 5.4998305387193387 + + + 21 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.60502446654887354 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.93416481023846032 + + + 21 + -2.7252571125147735 + -2.7406945936944584 + -2.8751558537650319 + -3.2406864635799923 + -3.5460149559446359 + -3.5687884755542472 + -3.4901303714495153 + -3.4970015262603624 + -3.4496621504416853 + -3.2125376886451624 + -2.91505863813991 + -2.7962936950351538 + -2.8888307484050659 + -2.9753285114361296 + -2.8869539267116591 + -2.7654523327441973 + -2.7283623342197525 + -2.7247454958847039 + -2.7246193286548039 + -2.7246176904526385 + -2.7246176904526385 + + + 21 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 4.3399332371624695 + 4.1747174038020365 + 4.1623670231442622 + 4.0396595028897746 + 4.1446557041188061 + 3.9896509587172271 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.9896509549207697 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + + diff --git a/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_7.xml b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_7.xml new file mode 100644 index 0000000000..35b41a8549 --- /dev/null +++ b/src/gromacs/awh/tests/refdata/WithParameters_BiasTest_ForcesBiasPmf_7.xml @@ -0,0 +1,130 @@ + + + + + 3 + stage: initial + convolve forces: yes + skip updates: yes + + + 21 + 0.21526724902654029 + 0.00037168043378189554 + 0.0023506021278633634 + 1.8322908294787776e-14 + 1.8182159860257569e-05 + -1.8182159861071263e-05 + -6.7883759693111072e-06 + 6.7883758926473613e-06 + -4.5547116425681544e-14 + 6.7883759521357456e-06 + -6.7883759527878661e-06 + -3.5538537690811558 + -2.0147107048387825 + -2.7052521678814725 + -1.0406813794780787 + -3.967066260592369 + -0.87907598448922164 + -1.6612940249811217 + -2.6184399315060456 + -1.2743003165852991 + -0.86472575028406273 + + + 21 + 5.3171052747656216 + 5.3139634951471235 + 5.3139852805648378 + 5.3139597783455503 + 5.313959928304433 + 5.313959928304433 + 5.3139598111771384 + 5.3139598111771384 + 5.3139597783455486 + 5.3139598111771358 + 5.3139598111771358 + 5.8523574407739236 + 5.909517708141518 + 5.5891055083437386 + 5.5179043538034485 + 5.6909400832286146 + 5.5084236091309347 + 5.4647638463620405 + 5.4221743541770948 + 5.4793615522962513 + 5.4998305387193387 + + + 21 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.60502446654887354 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.93416481023846032 + + + 21 + -2.7252571125147735 + -2.7406945936944584 + -2.8751558537650319 + -3.2406864635799923 + -3.5460149559446359 + -3.5687884755542472 + -3.4901303714495153 + -3.4970015262603624 + -3.4496621504416853 + -3.2125376886451624 + -2.91505863813991 + -2.7962936950351538 + -2.8888307484050659 + -2.9753285114361296 + -2.8869539267116591 + -2.7654523327441973 + -2.7283623342197525 + -2.7247454958847039 + -2.7246193286548039 + -2.7246176904526385 + -2.7246176904526385 + + + 21 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 4.3399332371624695 + 4.1747174038020365 + 4.1623670231442622 + 4.0396595028897746 + 4.1446557041188061 + 3.9896509587172271 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.9896509549207697 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + 3.7345175271931224 + + diff --git a/src/gromacs/fileio/checkpoint.cpp b/src/gromacs/fileio/checkpoint.cpp index cd45322924..5ae996b0ba 100644 --- a/src/gromacs/fileio/checkpoint.cpp +++ b/src/gromacs/fileio/checkpoint.cpp @@ -61,6 +61,7 @@ #include "gromacs/math/vec.h" #include "gromacs/math/vecdump.h" #include "gromacs/math/vectypes.h" +#include "gromacs/mdtypes/awh-history.h" #include "gromacs/mdtypes/commrec.h" #include "gromacs/mdtypes/df_history.h" #include "gromacs/mdtypes/edsamhistory.h" @@ -97,7 +98,7 @@ * But old code can not read a new entry that is present in the file * (but can read a new format when new entries are not present). */ -static const int cpt_version = 16; +static const int cpt_version = 17; const char *est_names[estNR] = @@ -156,6 +157,31 @@ const char *edfh_names[edfhNR] = "accumulated_plus", "accumulated_minus", "accumulated_plus_2", "accumulated_minus_2", "Tij", "Tij_empirical" }; +/* AWH biasing history variables */ +enum { + eawhhIN_INITIAL, + eawhhEQUILIBRATEHISTOGRAM, + eawhhHISTSIZE, + eawhhNPOINTS, + eawhhCOORDPOINT, eawhhUMBRELLAGRIDPOINT, + eawhhUPDATELIST, + eawhhLOGSCALEDSAMPLEWEIGHT, + eawhhNUMUPDATES, + eawhhNR +}; + +const char *eawhh_names[eawhhNR] = +{ + "awh_in_initial", + "awh_equilibrateHistogram", + "awh_histsize", + "awh_npoints", + "awh_coordpoint", "awh_umbrellaGridpoint", + "awh_updatelist", + "awh_logScaledSampleWeight", + "awh_numupdates" +}; + //! Higher level vector element type, only used for formatting checkpoint dumps enum class CptElementType { @@ -168,10 +194,11 @@ enum class CptElementType //! \brief Parts of the checkpoint state, only used for reporting enum class StatePart { - microState, //!< The microstate of the simulated system - kineticEnergy, //!< Kinetic energy, needed for T/P-coupling state - energyHistory, //!< Energy observable statistics - freeEnergyHistory //!< Free-energy state and observable statistics + microState, //!< The microstate of the simulated system + kineticEnergy, //!< Kinetic energy, needed for T/P-coupling state + energyHistory, //!< Energy observable statistics + freeEnergyHistory, //!< Free-energy state and observable statistics + accWeightHistogram //!< Accelerated weight histogram method state }; //! \brief Return the name of a checkpoint entry based on part and part entry @@ -179,10 +206,11 @@ static const char *entryName(StatePart part, int ecpt) { switch (part) { - case StatePart::microState: return est_names [ecpt]; - case StatePart::kineticEnergy: return eeks_names[ecpt]; - case StatePart::energyHistory: return eenh_names[ecpt]; - case StatePart::freeEnergyHistory: return edfh_names[ecpt]; + case StatePart::microState: return est_names [ecpt]; + case StatePart::kineticEnergy: return eeks_names[ecpt]; + case StatePart::energyHistory: return eenh_names[ecpt]; + case StatePart::freeEnergyHistory: return edfh_names[ecpt]; + case StatePart::accWeightHistogram: return eawhh_names[ecpt]; } return nullptr; @@ -845,7 +873,7 @@ static void do_cpt_header(XDR *xd, gmx_bool bRead, int *file_version, int *nnodes, int *dd_nc, int *npme, int *natoms, int *ngtc, int *nnhpres, int *nhchainlength, int *nlambda, int *flags_state, - int *flags_eks, int *flags_enh, int *flags_dfh, + int *flags_eks, int *flags_enh, int *flags_dfh, int *flags_awhh, int *nED, int *eSwapCoords, FILE *list) { @@ -988,6 +1016,7 @@ static void do_cpt_header(XDR *xd, gmx_bool bRead, int *file_version, { *nED = 0; } + if (*file_version >= 16) { do_cpt_int_err(xd, "swap", eSwapCoords, list); @@ -996,6 +1025,15 @@ static void do_cpt_header(XDR *xd, gmx_bool bRead, int *file_version, { *eSwapCoords = eswapNO; } + + if (*file_version >= 17) + { + do_cpt_int_err(xd, "AWH history flags", flags_awhh, list); + } + else + { + *flags_awhh = 0; + } } static int do_cpt_footer(XDR *xd, int file_version) @@ -1462,6 +1500,128 @@ static int do_cpt_EDstate(XDR *xd, gmx_bool bRead, return 0; } +static int do_cpt_awh_bias(XDR *xd, gmx_bool bRead, + int fflags, gmx::AwhBiasHistory *biasHistory, + FILE *list) +{ + int ret = 0; + + gmx::AwhBiasStateHistory *state = &biasHistory->state; + for (int i = 0; (i < eawhhNR && ret == 0); i++) + { + if (fflags & (1<in_initial, list); break; + case eawhhEQUILIBRATEHISTOGRAM: + do_cpt_int_err(xd, eawhh_names[i], &state->equilibrateHistogram, list); break; + case eawhhHISTSIZE: + do_cpt_double_err(xd, eawhh_names[i], &state->histSize, list); break; + case eawhhNPOINTS: + { + int numPoints; + if (!bRead) + { + numPoints = biasHistory->pointState.size(); + } + do_cpt_int_err(xd, eawhh_names[i], &numPoints, list); + if (bRead) + { + biasHistory->pointState.resize(numPoints); + } + } + break; + case eawhhCOORDPOINT: + for (auto &psh : biasHistory->pointState) + { + do_cpt_double_err(xd, eawhh_names[i], &psh.target, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.free_energy, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.bias, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.weightsum_iteration, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.weightsum_covering, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.weightsum_tot, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.weightsum_ref, list); + do_cpt_step_err(xd, eawhh_names[i], &psh.last_update_index, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.log_pmfsum, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.visits_iteration, list); + do_cpt_double_err(xd, eawhh_names[i], &psh.visits_tot, list); + } + break; + case eawhhUMBRELLAGRIDPOINT: + do_cpt_int_err(xd, eawhh_names[i], &(state->umbrellaGridpoint), list); break; + case eawhhUPDATELIST: + do_cpt_int_err(xd, eawhh_names[i], &(state->origin_index_updatelist), list); + do_cpt_int_err(xd, eawhh_names[i], &(state->end_index_updatelist), list); + break; + case eawhhLOGSCALEDSAMPLEWEIGHT: + do_cpt_double_err(xd, eawhh_names[i], &(state->logScaledSampleWeight), list); + do_cpt_double_err(xd, eawhh_names[i], &(state->maxLogScaledSampleWeight), list); + break; + case eawhhNUMUPDATES: + do_cpt_step_err(xd, eawhh_names[i], &(state->numUpdates), list); + break; + default: + gmx_fatal(FARGS, "Unknown awh history entry %d\n", i); + } + } + } + + return ret; +} + +static int do_cpt_awh(XDR *xd, gmx_bool bRead, + int fflags, gmx::AwhHistory *awhHistory, + FILE *list) +{ + int ret = 0; + + if (fflags != 0) + { + std::shared_ptr awhHistoryLocal; + + if (awhHistory == nullptr) + { + GMX_RELEASE_ASSERT(bRead, "do_cpt_awh should not be called for writing without an AwhHistory"); + + awhHistoryLocal = std::shared_ptr(new gmx::AwhHistory()); + awhHistory = awhHistoryLocal.get(); + } + + /* To be able to read and write the AWH data several parameters determining the layout of the AWH structs need to be known + (nbias, npoints, etc.). The best thing (?) would be to have these passed to this function. When writing to a checkpoint + these parameters are available in awh_history (after calling init_awh_history). When reading from a checkpoint though, there + is no initialized awh_history (it is initialized and set in this function). The AWH parameters have not always been read + at the time when this function is called for reading so I don't know how to pass them as input. Here, this is solved by + when writing a checkpoint, also storing parameters needed for future reading of the checkpoint. + + Another issue is that some variables that AWH checkpoints don't have a registered enum and string (e.g. nbias below). + One difficulty is the multilevel structure of the data which would need to be represented somehow. */ + + int numBias; + if (!bRead) + { + numBias = awhHistory->bias.size(); + } + do_cpt_int_err(xd, "awh_nbias", &numBias, list); + + if (bRead) + { + awhHistory->bias.resize(numBias); + } + for (auto &bias : awhHistory->bias) + { + ret = do_cpt_awh_bias(xd, bRead, fflags, &bias, list); + if (ret) + { + return ret; + } + } + do_cpt_double_err(xd, "awh_potential_offset", &awhHistory->potentialOffset, list); + } + return ret; +} static int do_cpt_files(XDR *xd, gmx_bool bRead, gmx_file_position_t **p_outputfiles, int *nfiles, @@ -1672,6 +1832,20 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep, flags_dfh = 0; } + int flags_awhh = 0; + if (state->awhHistory != nullptr && state->awhHistory->bias.size() > 0) + { + flags_awhh |= ( (1 << eawhhIN_INITIAL) | + (1 << eawhhEQUILIBRATEHISTOGRAM) | + (1 << eawhhHISTSIZE) | + (1 << eawhhNPOINTS) | + (1 << eawhhCOORDPOINT) | + (1 << eawhhUMBRELLAGRIDPOINT) | + (1 << eawhhUPDATELIST) | + (1 << eawhhLOGSCALEDSAMPLEWEIGHT) | + (1 << eawhhNUMUPDATES)); + } + /* We can check many more things now (CPU, acceleration, etc), but * it is highly unlikely to have two separate builds with exactly * the same version, user, time, and build host! @@ -1700,7 +1874,7 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep, &eIntegrator, &simulation_part, &step, &t, &nppnodes, DOMAINDECOMP(cr) ? domdecCells : nullptr, &npmenodes, &state->natoms, &state->ngtc, &state->nnhpres, - &state->nhchainlength, &nlambda, &state->flags, &flags_eks, &flags_enh, &flags_dfh, + &state->nhchainlength, &nlambda, &state->flags, &flags_eks, &flags_enh, &flags_dfh, &flags_awhh, &nED, &eSwapCoords, nullptr); @@ -1715,6 +1889,7 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep, (do_cpt_enerhist(gmx_fio_getxdr(fp), FALSE, flags_enh, enerhist, nullptr) < 0) || (do_cpt_df_hist(gmx_fio_getxdr(fp), flags_dfh, nlambda, &state->dfhist, nullptr) < 0) || (do_cpt_EDstate(gmx_fio_getxdr(fp), FALSE, nED, edsamhist, nullptr) < 0) || + (do_cpt_awh(gmx_fio_getxdr(fp), FALSE, flags_awhh, state->awhHistory.get(), NULL) < 0) || (do_cpt_swapstate(gmx_fio_getxdr(fp), FALSE, eSwapCoords, swaphist, nullptr) < 0) || (do_cpt_files(gmx_fio_getxdr(fp), FALSE, &outputfiles, &noutputfiles, nullptr, file_version) < 0)) @@ -1972,7 +2147,7 @@ static void read_checkpoint(const char *fn, FILE **pfplog, char buf[STEPSTRSIZE]; int eIntegrator_f, nppnodes_f, npmenodes_f; ivec dd_nc_f; - int natoms, ngtc, nnhpres, nhchainlength, nlambda, fflags, flags_eks, flags_enh, flags_dfh; + int natoms, ngtc, nnhpres, nhchainlength, nlambda, fflags, flags_eks, flags_enh, flags_dfh, flags_awhh; int nED, eSwapCoords; int ret; gmx_file_position_t *outputfiles; @@ -2003,7 +2178,7 @@ static void read_checkpoint(const char *fn, FILE **pfplog, &eIntegrator_f, simulation_part, step, t, &nppnodes_f, dd_nc_f, &npmenodes_f, &natoms, &ngtc, &nnhpres, &nhchainlength, &nlambda, - &fflags, &flags_eks, &flags_enh, &flags_dfh, + &fflags, &flags_eks, &flags_enh, &flags_dfh, &flags_awhh, &nED, &eSwapCoords, nullptr); if (bAppendOutputFiles && @@ -2170,6 +2345,17 @@ static void read_checkpoint(const char *fn, FILE **pfplog, cp_error(); } + if (flags_awhh != 0 && state->awhHistory == nullptr) + { + state->awhHistory = std::shared_ptr(new gmx::AwhHistory()); + } + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, + flags_awhh, state->awhHistory.get(), NULL); + if (ret) + { + cp_error(); + } + if (eSwapCoords != eswapNO && observablesHistory->swapHistory == nullptr) { observablesHistory->swapHistory = std::unique_ptr(new swaphistory_t {}); @@ -2392,7 +2578,7 @@ void read_checkpoint_part_and_step(const char *filename, int nppnodes, npme; ivec dd_nc; int nlambda; - int flags_eks, flags_enh, flags_dfh; + int flags_eks, flags_enh, flags_dfh, flags_awhh; double t; t_state state; int nED, eSwapCoords; @@ -2415,7 +2601,7 @@ void read_checkpoint_part_and_step(const char *filename, &version, &btime, &buser, &bhost, &double_prec, &fprog, &ftime, &eIntegrator, simulation_part, step, &t, &nppnodes, dd_nc, &npme, &state.natoms, &state.ngtc, &state.nnhpres, &state.nhchainlength, - &nlambda, &state.flags, &flags_eks, &flags_enh, &flags_dfh, + &nlambda, &state.flags, &flags_eks, &flags_enh, &flags_dfh, &flags_awhh, &nED, &eSwapCoords, nullptr); gmx_fio_close(fp); @@ -2432,7 +2618,7 @@ static void read_checkpoint_data(t_fileio *fp, int *simulation_part, int nppnodes, npme; ivec dd_nc; int nlambda; - int flags_eks, flags_enh, flags_dfh; + int flags_eks, flags_enh, flags_dfh, flags_awhh; int nED, eSwapCoords; int nfiles_loc; gmx_file_position_t *files_loc = nullptr; @@ -2442,7 +2628,7 @@ static void read_checkpoint_data(t_fileio *fp, int *simulation_part, &version, &btime, &buser, &bhost, &double_prec, &fprog, &ftime, &eIntegrator, simulation_part, step, t, &nppnodes, dd_nc, &npme, &state->natoms, &state->ngtc, &state->nnhpres, &state->nhchainlength, - &nlambda, &state->flags, &flags_eks, &flags_enh, &flags_dfh, + &nlambda, &state->flags, &flags_eks, &flags_enh, &flags_dfh, &flags_awhh, &nED, &eSwapCoords, nullptr); ret = do_cpt_state(gmx_fio_getxdr(fp), state->flags, state, nullptr); @@ -2476,6 +2662,13 @@ static void read_checkpoint_data(t_fileio *fp, int *simulation_part, cp_error(); } + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, + flags_awhh, state->awhHistory.get(), NULL); + if (ret) + { + cp_error(); + } + swaphistory_t swaphist = {}; ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, eSwapCoords, &swaphist, nullptr); if (ret) @@ -2580,7 +2773,7 @@ void list_checkpoint(const char *fn, FILE *out) double t; ivec dd_nc; int nlambda; - int flags_eks, flags_enh, flags_dfh; + int flags_eks, flags_enh, flags_dfh, flags_awhh;; int nED, eSwapCoords; int ret; gmx_file_position_t *outputfiles; @@ -2594,7 +2787,7 @@ void list_checkpoint(const char *fn, FILE *out) &eIntegrator, &simulation_part, &step, &t, &nppnodes, dd_nc, &npme, &state.natoms, &state.ngtc, &state.nnhpres, &state.nhchainlength, &nlambda, &state.flags, - &flags_eks, &flags_enh, &flags_dfh, &nED, &eSwapCoords, + &flags_eks, &flags_enh, &flags_dfh, &flags_awhh, &nED, &eSwapCoords, out); ret = do_cpt_state(gmx_fio_getxdr(fp), state.flags, &state, out); if (ret) @@ -2625,6 +2818,12 @@ void list_checkpoint(const char *fn, FILE *out) if (ret == 0) { + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, + flags_awhh, state.awhHistory.get(), out); + } + + if (ret == 0) + { swaphistory_t swaphist = {}; ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, eSwapCoords, &swaphist, out); } diff --git a/src/gromacs/fileio/tpxio.cpp b/src/gromacs/fileio/tpxio.cpp index ea17544abc..6e3db2cd43 100644 --- a/src/gromacs/fileio/tpxio.cpp +++ b/src/gromacs/fileio/tpxio.cpp @@ -52,6 +52,8 @@ #include "gromacs/fileio/gmxfio-xdr.h" #include "gromacs/math/units.h" #include "gromacs/math/vec.h" +#include "gromacs/mdtypes/awh-history.h" +#include "gromacs/mdtypes/awh-params.h" #include "gromacs/mdtypes/inputrec.h" #include "gromacs/mdtypes/md_enums.h" #include "gromacs/mdtypes/pull-params.h" @@ -115,6 +117,7 @@ enum tpxv { tpxv_ReplacePullPrintCOM12, /**< Replaced print-com-1, 2 with pull-print-com */ tpxv_PullExternalPotential, /**< Added pull type external potential */ tpxv_GenericParamsForElectricField, /**< Introduced KeyValueTree and moved electric field parameters */ + tpxv_AcceleratedWeightHistogram, /**< sampling with accelerated weight histogram method (AWH) */ tpxv_Count /**< the total number of tpxv versions */ }; @@ -655,6 +658,65 @@ static void do_fepvals(t_fileio *fio, t_lambda *fepvals, gmx_bool bRead, int fil } } +static void do_awhBias(t_fileio *fio, gmx::AwhBiasParams *awhBiasParams, gmx_bool bRead, + int gmx_unused file_version) +{ + gmx_fio_do_int(fio, awhBiasParams->eTarget); + gmx_fio_do_double(fio, awhBiasParams->targetBetaScaling); + gmx_fio_do_double(fio, awhBiasParams->targetCutoff); + gmx_fio_do_int(fio, awhBiasParams->eGrowth); + gmx_fio_do_int(fio, awhBiasParams->bUserData); + gmx_fio_do_double(fio, awhBiasParams->errorInitial); + gmx_fio_do_int(fio, awhBiasParams->ndim); + gmx_fio_do_int(fio, awhBiasParams->shareGroup); + gmx_fio_do_gmx_bool(fio, awhBiasParams->equilibrateHistogram); + + if (bRead) + { + snew(awhBiasParams->dimParams, awhBiasParams->ndim); + } + + for (int d = 0; d < awhBiasParams->ndim; d++) + { + gmx::AwhDimParams *dimParams = &awhBiasParams->dimParams[d]; + + gmx_fio_do_int(fio, dimParams->eCoordProvider); + gmx_fio_do_int(fio, dimParams->coordIndex); + gmx_fio_do_double(fio, dimParams->origin); + gmx_fio_do_double(fio, dimParams->end); + gmx_fio_do_double(fio, dimParams->period); + gmx_fio_do_double(fio, dimParams->forceConstant); + gmx_fio_do_double(fio, dimParams->diffusion); + gmx_fio_do_double(fio, dimParams->coordValueInit); + gmx_fio_do_double(fio, dimParams->coverDiameter); + } +} + +static void do_awh(t_fileio *fio, gmx::AwhParams *awhParams, gmx_bool bRead, + int gmx_unused file_version) +{ + gmx_fio_do_int(fio, awhParams->numBias); + gmx_fio_do_int(fio, awhParams->nstOut); + gmx_fio_do_int64(fio, awhParams->seed); + gmx_fio_do_int(fio, awhParams->nstSampleCoord); + gmx_fio_do_int(fio, awhParams->numSamplesUpdateFreeEnergy); + gmx_fio_do_int(fio, awhParams->ePotential); + gmx_fio_do_gmx_bool(fio, awhParams->shareBiasMultisim); + + if (awhParams->numBias > 0) + { + if (bRead) + { + snew(awhParams->awhBiasParams, awhParams->numBias); + } + + for (int k = 0; k < awhParams->numBias; k++) + { + do_awhBias(fio, &awhParams->awhBiasParams[k], bRead, file_version); + } + } +} + static void do_pull(t_fileio *fio, pull_params_t *pull, gmx_bool bRead, int file_version, int ePullOld) { @@ -1534,6 +1596,24 @@ static void do_inputrec(t_fileio *fio, t_inputrec *ir, gmx_bool bRead, ir->bPull = FALSE; } + if (file_version >= tpxv_AcceleratedWeightHistogram) + { + gmx_fio_do_gmx_bool(fio, ir->bDoAwh); + + if (ir->bDoAwh) + { + if (bRead) + { + snew(ir->awhParams, 1); + } + do_awh(fio, ir->awhParams, bRead, file_version); + } + } + else + { + ir->bDoAwh = FALSE; + } + /* Enforced rotation */ if (file_version >= 74) { diff --git a/src/gromacs/gmxpreprocess/grompp.cpp b/src/gromacs/gmxpreprocess/grompp.cpp index 1fa731d17e..eb85a4207c 100644 --- a/src/gromacs/gmxpreprocess/grompp.cpp +++ b/src/gromacs/gmxpreprocess/grompp.cpp @@ -48,6 +48,7 @@ #include +#include "gromacs/awh/read-params.h" #include "gromacs/commandline/pargs.h" #include "gromacs/ewald/ewald-utils.h" #include "gromacs/ewald/pme.h" @@ -2305,6 +2306,12 @@ int gmx_grompp(int argc, char *argv[]) * that providers have been registerd for all external potentials. */ + if (ir->bDoAwh) + { + setStateDependentAwhParams(ir->awhParams, ir->pull, pull, + state.box, ir->ePBC, &ir->opts, wi); + } + if (ir->bPull) { finish_pull(pull); diff --git a/src/gromacs/gmxpreprocess/readir.cpp b/src/gromacs/gmxpreprocess/readir.cpp index 9b7df159dd..18ec42bb75 100644 --- a/src/gromacs/gmxpreprocess/readir.cpp +++ b/src/gromacs/gmxpreprocess/readir.cpp @@ -47,6 +47,7 @@ #include #include +#include "gromacs/awh/read-params.h" #include "gromacs/fileio/readinp.h" #include "gromacs/fileio/warninp.h" #include "gromacs/gmxlib/chargegroup.h" @@ -2138,6 +2139,22 @@ void get_ir(const char *mdparin, const char *mdparout, is->pull_grp = read_pullparams(&ninp, &inp, ir->pull, wi); } + /* AWH biasing + NOTE: needs COM pulling input */ + CCTYPE("AWH biasing"); + EETYPE("awh", ir->bDoAwh, yesno_names); + if (ir->bDoAwh) + { + if (ir->bPull) + { + ir->awhParams = gmx::readAndCheckAwhParams(&ninp, &inp, ir, wi); + } + else + { + gmx_fatal(FARGS, "AWH biasing is only compatible with COM pulling turned on"); + } + } + /* Enforced rotation */ CCTYPE("ENFORCED ROTATION"); CTYPE("Enforced rotation: No or Yes"); diff --git a/src/gromacs/gmxpreprocess/readpull.cpp b/src/gromacs/gmxpreprocess/readpull.cpp index a25bcf603d..f732e23395 100644 --- a/src/gromacs/gmxpreprocess/readpull.cpp +++ b/src/gromacs/gmxpreprocess/readpull.cpp @@ -551,7 +551,7 @@ pull_t *set_pull_init(t_inputrec *ir, const gmx_mtop_t *mtop, pcrd->init = 0; } - get_pull_coord_value(pull_work, c, &pbc, &value); + value = get_pull_coord_value(pull_work, c, &pbc); value *= pull_conversion_factor_internal2userinput(pcrd); fprintf(stderr, " %10.3f %s", value, pull_coordinate_units(pcrd)); diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_EmptyInputWorks.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_EmptyInputWorks.xml index 60f8de8e51..8d2b356f90 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_EmptyInputWorks.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_EmptyInputWorks.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesDifferentKindsOfMdpLines.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesDifferentKindsOfMdpLines.xml index fb66fcfb2c..27684c38d6 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesDifferentKindsOfMdpLines.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesDifferentKindsOfMdpLines.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesOnlyCutoffScheme.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesOnlyCutoffScheme.xml index 340125eaf3..0d8780d26b 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesOnlyCutoffScheme.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_HandlesOnlyCutoffScheme.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricField.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricField.xml index 33efa01144..353f36905d 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricField.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricField.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldOscillating.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldOscillating.xml index 8d0ab5d5a9..538ebd3da9 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldOscillating.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldOscillating.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldPulsed.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldPulsed.xml index 57271f12dc..0de50f50c7 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldPulsed.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_ProducesOutputFromElectricFieldPulsed.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_UserErrorsSilentlyTolerated.xml b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_UserErrorsSilentlyTolerated.xml index 60f8de8e51..8d2b356f90 100644 --- a/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_UserErrorsSilentlyTolerated.xml +++ b/src/gromacs/gmxpreprocess/tests/refdata/GetIrTest_UserErrorsSilentlyTolerated.xml @@ -243,6 +243,9 @@ wall-ewald-zfac = 3 ; COM PULLING pull = no +; AWH biasing +awh = no + ; ENFORCED ROTATION ; Enforced rotation: No or Yes rotation = no diff --git a/src/gromacs/mdlib/broadcaststructs.cpp b/src/gromacs/mdlib/broadcaststructs.cpp index 76264af313..cf18796693 100644 --- a/src/gromacs/mdlib/broadcaststructs.cpp +++ b/src/gromacs/mdlib/broadcaststructs.cpp @@ -45,6 +45,7 @@ #include "gromacs/math/vec.h" #include "gromacs/mdlib/mdrun.h" #include "gromacs/mdlib/tgroup.h" +#include "gromacs/mdtypes/awh-params.h" #include "gromacs/mdtypes/commrec.h" #include "gromacs/mdtypes/inputrec.h" #include "gromacs/mdtypes/md_enums.h" @@ -452,6 +453,26 @@ static void bc_grpopts(const t_commrec *cr, t_grpopts *g) } } +static void bc_awhBias(const t_commrec *cr, gmx::AwhBiasParams *awhBiasParams) +{ + block_bc(cr, *awhBiasParams); + + snew_bc(cr, awhBiasParams->dimParams, awhBiasParams->ndim); + nblock_bc(cr, awhBiasParams->ndim, awhBiasParams->dimParams); +} + +static void bc_awh(const t_commrec *cr, gmx::AwhParams *awhParams) +{ + int k; + + block_bc(cr, *awhParams); + snew_bc(cr, awhParams->awhBiasParams, awhParams->numBias); + for (k = 0; k < awhParams->numBias; k++) + { + bc_awhBias(cr, &awhParams->awhBiasParams[k]); + } +} + static void bc_pull_group(const t_commrec *cr, t_pull_group *pgrp) { block_bc(cr, *pgrp); @@ -688,6 +709,12 @@ static void bc_inputrec(const t_commrec *cr, t_inputrec *inputrec) snew_bc(cr, inputrec->pull, 1); bc_pull(cr, inputrec->pull); } + if (inputrec->bDoAwh) + { + snew_bc(cr, inputrec->awhParams, 1); + bc_awh(cr, inputrec->awhParams); + } + if (inputrec->bRot) { snew_bc(cr, inputrec->rot, 1); diff --git a/src/gromacs/mdlib/sim_util.cpp b/src/gromacs/mdlib/sim_util.cpp index 7f625a3b0d..5f240212df 100644 --- a/src/gromacs/mdlib/sim_util.cpp +++ b/src/gromacs/mdlib/sim_util.cpp @@ -48,6 +48,7 @@ #include +#include "gromacs/awh/awh.h" #include "gromacs/domdec/dlbtiming.h" #include "gromacs/domdec/domdec.h" #include "gromacs/domdec/domdec_struct.h" @@ -720,6 +721,7 @@ static void checkPotentialEnergyValidity(const gmx_enerdata_t *enerd) * global communication at the end, so global barriers within the MD loop * are as close together as possible. * + * \param[in] fplog The log file * \param[in] cr The communication record * \param[in] inputrec The input record * \param[in] step The current MD step @@ -740,7 +742,8 @@ static void checkPotentialEnergyValidity(const gmx_enerdata_t *enerd) * \todo Convert all other algorithms called here to ForceProviders. */ static void -computeSpecialForces(t_commrec *cr, +computeSpecialForces(FILE *fplog, + t_commrec *cr, t_inputrec *inputrec, gmx_int64_t step, double t, @@ -773,6 +776,15 @@ computeSpecialForces(t_commrec *cr, forceWithVirial, mdatoms, enerd, lambda, t, wcycle); + + if (inputrec->bDoAwh) + { + Awh &awh = *inputrec->awh; + enerd->term[F_COM_PULL] += + awh.applyBiasForcesAndUpdateBias(inputrec->ePBC, *mdatoms, box, + forceWithVirial, + t, step, wcycle, fplog); + } } rvec *f = as_rvec_array(forceWithVirial->force_.data()); @@ -1353,7 +1365,7 @@ static void do_force_cutsVERLET(FILE *fplog, t_commrec *cr, wallcycle_stop(wcycle, ewcFORCE); - computeSpecialForces(cr, inputrec, step, t, wcycle, + computeSpecialForces(fplog, cr, inputrec, step, t, wcycle, fr->forceProviders, box, x, mdatoms, lambda, flags, &forceWithVirial, enerd, ed, bNS); @@ -1817,7 +1829,7 @@ static void do_force_cutsGROUP(FILE *fplog, t_commrec *cr, } } - computeSpecialForces(cr, inputrec, step, t, wcycle, + computeSpecialForces(fplog, cr, inputrec, step, t, wcycle, fr->forceProviders, box, x, mdatoms, lambda, flags, &forceWithVirial, enerd, ed, bNS); diff --git a/src/gromacs/mdtypes/awh-history.h b/src/gromacs/mdtypes/awh-history.h new file mode 100644 index 0000000000..12cf6dede6 --- /dev/null +++ b/src/gromacs/mdtypes/awh-history.h @@ -0,0 +1,133 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \libinternal \file + * + * \brief + * Contains datatypes and function declarations needed by AWH to + * have its data checkpointed. + * + * \author Viveca Lindahl + * \inlibraryapi + * \ingroup module_mdtypes + */ + +#ifndef GMX_MDTYPES_AWHHISTORY_H +#define GMX_MDTYPES_AWHHISTORY_H + +#include + +#include "gromacs/utility/basedefinitions.h" + +namespace gmx +{ + +/*! \cond INTERNAL */ + +//! Grid point state history data. +struct AwhPointStateHistory +{ + double bias; /**< Current biasing function estimate */ + double free_energy; /**< Current estimate of the convolved free energy/PMF. */ + double target; /**< Current target distribution, normalized to 1 */ + double weightsum_iteration; /**< Accumulated weight this iteration (1 replica) */ + double weightsum_covering; /**< Accumulated weights for covering checks */ + double weightsum_tot; /**< Accumulated weights, never reset */ + double weightsum_ref; /**< The reference weight histogram determining the f updates */ + gmx_int64_t last_update_index; /**< The last update that was performed at this point. */ + double log_pmfsum; /**< Logarithm of the PMF histogram (for 1 replica) */ + double visits_iteration; /**< Visits to this bin this iteration (1 replica) */ + double visits_tot; /**< Accumulated visits to this bin */ +}; + +//! The global AWH bias history state, contains most data of the corresponding struct in awh.h. +struct AwhBiasStateHistory +{ + int umbrellaGridpoint; /**< Index for the current umbrella reference coordinate point (for umbrella potential type) */ + int origin_index_updatelist; /**< Point index of the origin of the subgrid that has been touched since last update. */ + int end_index_updatelist; /**< Point index of the end of the subgrid that has been touched since last update. */ + int in_initial; /**< True if in the intial stage. */ + int equilibrateHistogram; /**< True if histogram needs equilibration. */ + double histSize; /**< Size of reference weight histogram. */ + double logScaledSampleWeight; /**< The log of the current sample weight, scaled because of the histogram rescaling. */ + double maxLogScaledSampleWeight; /**< Maximum sample weight obtained for previous (smaller) histogram sizes. */ + gmx_int64_t numUpdates; /**< The number of updates. */ + + /*! \brief Constructor. */ + AwhBiasStateHistory() : umbrellaGridpoint(0), + origin_index_updatelist(0), + end_index_updatelist(0), + in_initial(0), + equilibrateHistogram(0), + histSize(0), + logScaledSampleWeight(0), + maxLogScaledSampleWeight(0), + numUpdates(0) + { + } +}; + +//! AWH bias history data. Note that this is a copy of an AWH internal struct. +struct AwhBiasHistory +{ + std::vector pointState; /**< History for grid coordinate points. */ + + AwhBiasStateHistory state; /**< The global state of the AWH bias. */ + + /*! \brief Constructor. */ + AwhBiasHistory() : pointState(), + state() + { + } +}; + +//! A collection of AWH bias history data. */ +struct AwhHistory +{ + std::vector bias; /**< History for each bias. */ + double potentialOffset; /**< The offset of the bias potential due to bias updates. */ + + /*! \brief Constructor. */ + AwhHistory() : bias(), + potentialOffset(0) + { + } +}; + +/*! \endcond */ + +} // namespace gmx + +#endif diff --git a/src/gromacs/mdtypes/awh-params.h b/src/gromacs/mdtypes/awh-params.h new file mode 100644 index 0000000000..77f23bbc95 --- /dev/null +++ b/src/gromacs/mdtypes/awh-params.h @@ -0,0 +1,145 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ + +/*! \libinternal \file + * + * \brief + * Declares AWH parameter data types. + * + * Besides internal use by the AWH module, the AWH parameters are needed + * for reading the user input (mdp) file and for reading and writing the + * parameters to the mdrun input (tpr) file. + * + * \author Viveca Lindahl + * \inlibraryapi + * \ingroup module_mdtypes + */ + +#ifndef GMX_MDTYPES_AWH_PARAMS_H +#define GMX_MDTYPES_AWH_PARAMS_H + +#include "gromacs/mdtypes/md_enums.h" +#include "gromacs/utility/basedefinitions.h" + +namespace gmx +{ + +//! Target distribution enum. +enum { + eawhtargetCONSTANT, eawhtargetCUTOFF, eawhtargetBOLTZMANN, eawhtargetLOCALBOLTZMANN, eawhtargetNR +}; +//! String for target distribution. +extern const char *eawhtarget_names[eawhtargetNR+1]; +//! Macro for target distribution string. +#define EAWHTARGET(e) enum_name(e, gmx::eawhtargetNR, gmx::eawhtarget_names) + +//! Weight histogram growth enum. +enum { + eawhgrowthEXP_LINEAR, eawhgrowthLINEAR, eawhgrowthNR +}; +//! String for weight histogram growth +extern const char *eawhgrowth_names[eawhgrowthNR+1]; +//! Macro for weight histogram growth string. +#define EAWHGROWTH(e) enum_name(e, gmx::eawhgrowthNR, gmx::eawhgrowth_names) + +//! AWH potential type enum. +enum { + eawhpotentialCONVOLVED, eawhpotentialUMBRELLA, eawhpotentialNR +}; +//! String for AWH potential type +extern const char *eawhpotential_names[eawhpotentialNR+1]; +//! Macro for AWH potential type string. +#define EAWHPOTENTIAL(e) enum_name(e, gmx::eawhpotentialNR, gmx::eawhpotential_names) + +//! AWH bias reaction coordinate provider +enum { + eawhcoordproviderPULL, eawhcoordproviderNR +}; +//! String for AWH bias reaction coordinate provider. +extern const char *eawhcoordprovider_names[eawhcoordproviderNR+1]; +//! Macro for AWH bias reaction coordinate provider. +#define EAWHCOORDPROVIDER(e) enum_name(e, gmx::eawhcoordproviderNR, gmx::eawhcoordprovider_names) + +/*! \cond INTERNAL */ + +//! Parameters for an AWH coordinate dimension. +struct AwhDimParams +{ + int eCoordProvider; /**< The module providing the reaction coordinate. */ + int coordIndex; /**< Index of reaction coordinate in the provider. */ + double origin; /**< Start value of the interval. */ + double end; /**< End value of the interval. */ + double period; /**< The period of this dimension (= 0 if not periodic). */ + double forceConstant; /**< The force constant in kJ/mol/nm^2, kJ/mol/rad^2 */ + double diffusion; /**< Estimated diffusion constant in units of nm^2/ps or rad^2/ps. */ + double coordValueInit; /**< The initial coordinate value. */ + double coverDiameter; /**< The diameter that needs to be sampled around a point before it is considered covered. */ +}; + +//! Parameters for an AWH bias. +struct AwhBiasParams +{ + // TODO: Turn dimParams into a std::vector when moved into AWH module + int ndim; /**< Dimension of the coordinate space. */ + AwhDimParams *dimParams; /**< AWH parameters per dimension. */ + int eTarget; /**< Type of target distribution. */ + double targetBetaScaling; /**< Beta scaling value for Boltzmann type target distributions. */ + double targetCutoff; /**< Free energy cutoff value for cutoff type target distribution in kJ/mol.*/ + int eGrowth; /**< How the biasing histogram grows. */ + int bUserData; /**< Is there a user-defined initial PMF estimate and target estimate? */ + double errorInitial; /**< Estimated initial free energy error in kJ/mol. */ + int shareGroup; /**< When >0, the bias is shared with biases of the same group and across multiple simulations when shareBiasMultisim=true */ + gmx_bool equilibrateHistogram; /**< True if the simulation starts out by equilibrating the histogram. */ +}; + +//! Parameters for AWH. +struct AwhParams +{ + // TODO: Turn awhBiasParams into a std::vector when moved into AWH module + int numBias; /**< The number of AWH biases.*/ + AwhBiasParams *awhBiasParams; /**< AWH bias parameters.*/ + gmx_int64_t seed; /**< Random seed.*/ + int nstOut; /**< Output step interval.*/ + int nstSampleCoord; /**< Number of samples per coordinate sample (also used for PMF) */ + int numSamplesUpdateFreeEnergy; /**< Number of samples per free energy update. */ + int ePotential; /**< Type of potential. */ + gmx_bool shareBiasMultisim; /**< When true, share biases with shareGroup>0 between multi-simulations */ +}; + +/*! \endcond */ + +} // namespace gmx + +#endif /* GMX_MDTYPES_AWH_PARAMS_H */ diff --git a/src/gromacs/mdtypes/forceoutput.h b/src/gromacs/mdtypes/forceoutput.h index ac9ab13593..60752d7c7e 100644 --- a/src/gromacs/mdtypes/forceoutput.h +++ b/src/gromacs/mdtypes/forceoutput.h @@ -74,7 +74,13 @@ class ForceWithVirial force_(force), computeVirial_(computeVirial) { - clear_mat(virial_); + for (int dim1 = 0; dim1 < DIM; dim1++) + { + for (int dim2 = 0; dim2 < DIM; dim2++) + { + virial_[dim1][dim2] = 0; + } + } } /*! \brief Adds a virial contribution diff --git a/src/gromacs/mdtypes/inputrec.cpp b/src/gromacs/mdtypes/inputrec.cpp index a8a415b25d..665e6563c4 100644 --- a/src/gromacs/mdtypes/inputrec.cpp +++ b/src/gromacs/mdtypes/inputrec.cpp @@ -46,6 +46,7 @@ #include "gromacs/math/veccompare.h" #include "gromacs/math/vecdump.h" +#include "gromacs/mdtypes/awh-params.h" #include "gromacs/mdtypes/md_enums.h" #include "gromacs/mdtypes/pull-params.h" #include "gromacs/pbcutil/pbc.h" @@ -654,6 +655,83 @@ static void pr_pull(FILE *fp, int indent, const pull_params_t *pull) } } +static void pr_awh_bias_dim(FILE *fp, int indent, gmx::AwhDimParams *awhDimParams, char *prefix) +{ + pr_indent(fp, indent); + indent++; + fprintf(fp, "%s:\n", prefix); + PS("coord-provider", EAWHCOORDPROVIDER(awhDimParams->eCoordProvider)); + PI("coord-index", awhDimParams->coordIndex + 1); + PR("start", awhDimParams->origin); + PR("end", awhDimParams->end); + PR("period", awhDimParams->period); + PR("force-constant", awhDimParams->forceConstant); + PR("diffusion", awhDimParams->diffusion); + PR("start", awhDimParams->origin); + PR("end", awhDimParams->end); + PR("cover-diameter", awhDimParams->coverDiameter); +} + +static void pr_awh_bias(FILE *fp, int indent, gmx::AwhBiasParams *awhBiasParams, char *prefix) +{ + char opt[STRLEN]; + + sprintf(opt, "%s-error-init", prefix); + PR(opt, awhBiasParams->errorInitial); + sprintf(opt, "%s-growth", prefix); + PS(opt, EAWHGROWTH(awhBiasParams->eGrowth)); + sprintf(opt, "%s-target", prefix); + PS(opt, EAWHTARGET(awhBiasParams->eTarget)); + sprintf(opt, "%s-target-beta-scalng", prefix); + PR(opt, awhBiasParams->targetBetaScaling); + sprintf(opt, "%s-target-cutoff", prefix); + PR(opt, awhBiasParams->targetCutoff); + sprintf(opt, "%s-user-data", prefix); + PS(opt, EBOOL(awhBiasParams->bUserData)); + sprintf(opt, "%s-share-group", prefix); + PI(opt, awhBiasParams->shareGroup); + sprintf(opt, "%s-equilibrate-histogram", prefix); + PS(opt, EBOOL(awhBiasParams->equilibrateHistogram)); + sprintf(opt, "%s-ndim", prefix); + PI(opt, awhBiasParams->ndim); + + for (int d = 0; d < awhBiasParams->ndim; d++) + { + char prefixdim[STRLEN]; + sprintf(prefixdim, "%s-dim%d", prefix, d + 1); + pr_awh_bias_dim(fp, indent, &awhBiasParams->dimParams[d], prefixdim); + } +} + +static void pr_awh(FILE *fp, int indent, gmx::AwhParams *awhParams) +{ + int k; + char opt[STRLEN], prefix[STRLEN]; + + sprintf(prefix, "%s", "awh"); + + sprintf(opt, "%s-potential", prefix); + PS(opt, EAWHPOTENTIAL(awhParams->ePotential)); + sprintf(opt, "%s-seed", prefix); + PI(opt, awhParams->seed); + sprintf(opt, "%s-nstout", prefix); + PI(opt, awhParams->nstOut); + sprintf(opt, "%s-nstsample", prefix); + PI(opt, awhParams->nstSampleCoord); + sprintf(opt, "%s-nsamples-update", prefix); + PI(opt, awhParams->numSamplesUpdateFreeEnergy); + sprintf(opt, "%s-share-bias-multisim", prefix); + PS(opt, EBOOL(awhParams->shareBiasMultisim)); + sprintf(opt, "%s-nbias", prefix); + PI(opt, awhParams->numBias); + + for (k = 0; k < awhParams->numBias; k++) + { + sprintf(prefix, "awh%d", k + 1); + pr_awh_bias(fp, indent, &awhParams->awhBiasParams[k], prefix); + } +} + static void pr_rotgrp(FILE *fp, int indent, int g, const t_rotgrp *rotg) { pr_indent(fp, indent); @@ -928,6 +1006,13 @@ void pr_inputrec(FILE *fp, int indent, const char *title, const t_inputrec *ir, pr_pull(fp, indent, ir->pull); } + /* AWH BIASING */ + PS("awh", EBOOL(ir->bDoAwh)); + if (ir->bDoAwh) + { + pr_awh(fp, indent, ir->awhParams); + } + /* ENFORCED ROTATION */ PS("rotation", EBOOL(ir->bRot)); if (ir->bRot) @@ -1063,6 +1148,56 @@ static void cmp_pull(FILE *fp) fprintf(fp, "WARNING: Both files use COM pulling, but comparing of the pull struct is not implemented (yet). The pull parameters could be the same or different.\n"); } +static void cmp_awhDimParams(FILE *fp, const gmx::AwhDimParams *dimp1, const gmx::AwhDimParams *dimp2, int dimIndex, real ftol, real abstol) +{ + /* Note that we have double index here, but the compare functions only + * support one index, so here we only print the dim index and not the bias. + */ + cmp_int(fp, "inputrec.awhParams->bias?->dim->coord_index", dimIndex, dimp1->coordIndex, dimp2->coordIndex); + cmp_double(fp, "inputrec->awhParams->bias?->dim->period", dimIndex, dimp1->period, dimp2->period, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->bias?->dim->diffusion", dimIndex, dimp1->diffusion, dimp2->diffusion, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->bias?->dim->origin", dimIndex, dimp1->origin, dimp2->origin, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->bias?->dim->end", dimIndex, dimp1->end, dimp2->end, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->bias?->dim->coord_value_init", dimIndex, dimp1->coordValueInit, dimp2->coordValueInit, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->bias?->dim->coverDiameter", dimIndex, dimp1->coverDiameter, dimp2->coverDiameter, ftol, abstol); +} + +static void cmp_awhBiasParams(FILE *fp, const gmx::AwhBiasParams *bias1, const gmx::AwhBiasParams *bias2, int biasIndex, real ftol, real abstol) +{ + cmp_int(fp, "inputrec->awhParams->ndim", biasIndex, bias1->ndim, bias2->ndim); + cmp_int(fp, "inputrec->awhParams->biaseTarget", biasIndex, bias1->eTarget, bias2->eTarget); + cmp_double(fp, "inputrec->awhParams->biastargetBetaScaling", biasIndex, bias1->targetBetaScaling, bias2->targetBetaScaling, ftol, abstol); + cmp_double(fp, "inputrec->awhParams->biastargetCutoff", biasIndex, bias1->targetCutoff, bias2->targetCutoff, ftol, abstol); + cmp_int(fp, "inputrec->awhParams->biaseGrowth", biasIndex, bias1->eGrowth, bias2->eGrowth); + cmp_bool(fp, "inputrec->awhParams->biasbUserData", biasIndex, bias1->bUserData, bias2->bUserData); + cmp_double(fp, "inputrec->awhParams->biaserror_initial", biasIndex, bias1->errorInitial, bias2->errorInitial, ftol, abstol); + cmp_int(fp, "inputrec->awhParams->biasShareGroup", biasIndex, bias1->shareGroup, bias2->shareGroup); + + for (int dim = 0; dim < std::min(bias1->ndim, bias2->ndim); dim++) + { + cmp_awhDimParams(fp, &bias1->dimParams[dim], &bias2->dimParams[dim], dim, ftol, abstol); + } +} + +static void cmp_awhParams(FILE *fp, const gmx::AwhParams *awh1, const gmx::AwhParams *awh2, real ftol, real abstol) +{ + cmp_int(fp, "inputrec->awhParams->nbias", -1, awh1->numBias, awh2->numBias); + cmp_int64(fp, "inputrec->awhParams->seed", awh1->seed, awh2->seed); + cmp_int(fp, "inputrec->awhParams->nstout", -1, awh1->nstOut, awh2->nstOut); + cmp_int(fp, "inputrec->awhParams->nstsample_coord", -1, awh1->nstSampleCoord, awh2->nstSampleCoord); + cmp_int(fp, "inputrec->awhParams->nsamples_update_free_energy", -1, awh1->numSamplesUpdateFreeEnergy, awh2->numSamplesUpdateFreeEnergy); + cmp_int(fp, "inputrec->awhParams->ePotential", -1, awh1->ePotential, awh2->ePotential); + cmp_bool(fp, "inputrec->awhParams->shareBiasMultisim", -1, awh1->shareBiasMultisim, awh2->shareBiasMultisim); + + if (awh1->numBias == awh2->numBias) + { + for (int bias = 0; bias < awh1->numBias; bias++) + { + cmp_awhBiasParams(fp, &awh1->awhBiasParams[bias], &awh2->awhBiasParams[bias], bias, ftol, abstol); + } + } +} + static void cmp_simtempvals(FILE *fp, const t_simtemp *simtemp1, const t_simtemp *simtemp2, int n_lambda, real ftol, real abstol) { int i; @@ -1249,6 +1384,12 @@ void cmp_inputrec(FILE *fp, const t_inputrec *ir1, const t_inputrec *ir2, real f cmp_pull(fp); } + cmp_bool(fp, "inputrec->bDoAwh", -1, ir1->bDoAwh, ir2->bDoAwh); + if (ir1->bDoAwh && ir2->bDoAwh) + { + cmp_awhParams(fp, ir1->awhParams, ir2->awhParams, ftol, abstol); + } + cmp_int(fp, "inputrec->eDisre", -1, ir1->eDisre, ir2->eDisre); cmp_real(fp, "inputrec->dr_fc", -1, ir1->dr_fc, ir2->dr_fc, ftol, abstol); cmp_int(fp, "inputrec->eDisreWeighting", -1, ir1->eDisreWeighting, ir2->eDisreWeighting); diff --git a/src/gromacs/mdtypes/inputrec.h b/src/gromacs/mdtypes/inputrec.h index f717846a61..4094bc02fd 100644 --- a/src/gromacs/mdtypes/inputrec.h +++ b/src/gromacs/mdtypes/inputrec.h @@ -37,7 +37,9 @@ #ifndef GMX_MDTYPES_INPUTREC_H #define GMX_MDTYPES_INPUTREC_H -#include +#include + +#include #include "gromacs/math/vectypes.h" #include "gromacs/mdtypes/md_enums.h" @@ -51,6 +53,8 @@ struct pull_params_t; namespace gmx { +class Awh; +struct AwhParams; class KeyValueTreeObject; } @@ -354,7 +358,14 @@ struct t_inputrec /* COM pulling data */ gmx_bool bPull; /* Do we do COM pulling? */ struct pull_params_t *pull; /* The data for center of mass pulling */ - struct pull_t *pull_work; /* The COM pull force calculation data structure; TODO this pointer should live somewhere else */ + // TODO: Remove this by converting pull into a ForceProvider + struct pull_t *pull_work; /* The COM pull force calculation data structure */ + + /* AWH bias data */ + gmx_bool bDoAwh; /* Use awh biasing for PMF calculations? */ + gmx::AwhParams *awhParams; /* AWH biasing parameters */ + // TODO: Remove this by converting AWH into a ForceProvider + gmx::Awh *awh; /* AWH work object */ /* Enforced rotation data */ gmx_bool bRot; /* Calculate enforced rotation potential(s)? */ diff --git a/src/gromacs/mdtypes/state.cpp b/src/gromacs/mdtypes/state.cpp index babe11959e..11056acbc2 100644 --- a/src/gromacs/mdtypes/state.cpp +++ b/src/gromacs/mdtypes/state.cpp @@ -46,6 +46,7 @@ #include "gromacs/math/paddedvector.h" #include "gromacs/math/vec.h" #include "gromacs/math/veccompare.h" +#include "gromacs/mdtypes/awh-history.h" #include "gromacs/mdtypes/df_history.h" #include "gromacs/mdtypes/inputrec.h" #include "gromacs/mdtypes/md_enums.h" @@ -244,6 +245,7 @@ t_state::t_state() : natoms(0), ekinstate(), hist(), dfhist(nullptr), + awhHistory(nullptr), ddp_count(0), ddp_count_cg_gl(0), cg_gl() diff --git a/src/gromacs/mdtypes/state.h b/src/gromacs/mdtypes/state.h index 9cab97a2b0..b13e9d216a 100644 --- a/src/gromacs/mdtypes/state.h +++ b/src/gromacs/mdtypes/state.h @@ -55,6 +55,7 @@ #define GMX_MDTYPES_STATE_H #include +#include #include #include "gromacs/math/paddedvector.h" @@ -65,6 +66,11 @@ struct t_inputrec; +namespace gmx +{ +struct AwhHistory; +} + /* * The t_state struct should contain all the (possibly) non-static * information required to define the state of the system. @@ -211,12 +217,13 @@ class t_state ekinstate_t ekinstate; //!< The state of the kinetic energy /* History for special algorithms, should be moved to a history struct */ - history_t hist; //!< Time history for restraints - df_history_t *dfhist; //!< Free-energy history for free energy analysis + history_t hist; //!< Time history for restraints + df_history_t *dfhist; //!< Free-energy history for free energy analysis + std::shared_ptr awhHistory; //!< Accelerated weight histogram history - int ddp_count; //!< The DD partitioning count for this state - int ddp_count_cg_gl; //!< The DD partitioning count for index_gl - std::vector cg_gl; //!< The global cg number of the local cgs + int ddp_count; //!< The DD partitioning count for this state + int ddp_count_cg_gl; //!< The DD partitioning count for index_gl + std::vector cg_gl; //!< The global cg number of the local cgs }; #ifndef DOXYGEN diff --git a/src/gromacs/pulling/pull.cpp b/src/gromacs/pulling/pull.cpp index 5469a3fe26..9a47202041 100644 --- a/src/gromacs/pulling/pull.cpp +++ b/src/gromacs/pulling/pull.cpp @@ -80,7 +80,7 @@ #include "gromacs/utility/real.h" #include "gromacs/utility/smalloc.h" -static bool pull_coordinate_is_angletype(const t_pull_coord *pcrd) +bool pull_coordinate_is_angletype(const t_pull_coord *pcrd) { return (pcrd->eGeom == epullgANGLE || pcrd->eGeom == epullgDIHEDRAL || @@ -964,14 +964,13 @@ static double get_pull_coord_deviation(struct pull_t *pull, return dev; } -void get_pull_coord_value(struct pull_t *pull, - int coord_ind, - const t_pbc *pbc, - double *value) +double get_pull_coord_value(struct pull_t *pull, + int coord_ind, + const t_pbc *pbc) { get_pull_coord_distance(pull, coord_ind, pbc); - *value = pull->coord[coord_ind].value; + return pull->coord[coord_ind].value; } static void clear_pull_forces_coord(pull_coord_work_t *pcrd) @@ -1650,11 +1649,11 @@ static void check_external_potential_registration(const struct pull_t *pull) * potential energy is added either to the pull term or to a term * specific to the external module. */ -void apply_external_pull_coord_force(struct pull_t *pull, - int coord_index, - double coord_force, - const t_mdatoms *mdatoms, - rvec *force, tensor virial) +void apply_external_pull_coord_force(struct pull_t *pull, + int coord_index, + double coord_force, + const t_mdatoms *mdatoms, + gmx::ForceWithVirial *forceWithVirial) { pull_coord_work_t *pcrd; @@ -1675,9 +1674,15 @@ void apply_external_pull_coord_force(struct pull_t *pull, calc_pull_coord_vector_force(pcrd); /* Add the forces for this coordinate to the total virial and force */ - add_virial_coord(virial, pcrd); + if (forceWithVirial->computeVirial_) + { + matrix virial = { { 0 } }; + add_virial_coord(virial, pcrd); + forceWithVirial->addVirialContribution(virial); + } - apply_forces_coord(pull, coord_index, mdatoms, force); + apply_forces_coord(pull, coord_index, mdatoms, + as_rvec_array(forceWithVirial->force_.data())); } pull->numExternalPotentialsStillToBeAppliedThisStep--; diff --git a/src/gromacs/pulling/pull.h b/src/gromacs/pulling/pull.h index e6927971bc..900d92e580 100644 --- a/src/gromacs/pulling/pull.h +++ b/src/gromacs/pulling/pull.h @@ -73,6 +73,13 @@ namespace gmx class ForceWithVirial; } +/*! \brief Returns if the pull coordinate is an angle + * + * \param[in] pcrd The pull coordinate to query the type for. + * \returns a boolean telling if the coordinate is of angle type. + */ +bool pull_coordinate_is_angletype(const t_pull_coord *pcrd); + /*! \brief Returns the units of the pull coordinate. * * \param[in] pcrd The pull coordinate to query the units for. @@ -99,13 +106,11 @@ double pull_conversion_factor_internal2userinput(const t_pull_coord *pcrd); * \param[in,out] pull The pull struct. * \param[in] coord_ind Number of the pull coordinate. * \param[in] pbc Information structure about periodicity. - * \param[out] value The value of the pull coordinate. + * \returns the value of the pull coordinate. */ -void get_pull_coord_value(struct pull_t *pull, - int coord_ind, - const struct t_pbc *pbc, - double *value); - +double get_pull_coord_value(struct pull_t *pull, + int coord_ind, + const struct t_pbc *pbc); /*! \brief Registers the provider of an external potential for a coordinate. * @@ -145,19 +150,17 @@ void register_external_pull_potential(struct pull_t *pull, * This function should be called after pull_potential has been called and, * obviously, before the coordinates are updated uses the forces. * - * \param[in,out] pull The pull struct. - * \param[in] coord_index The pull coordinate index to set the force for. - * \param[in] coord_force The scalar force for the pull coordinate. - * \param[in] mdatoms Atom properties, only masses are used. - * \param[in,out] force The force buffer. - * \param[in,out] virial The virial, can be NULL. + * \param[in,out] pull The pull struct. + * \param[in] coord_index The pull coordinate index to set the force for. + * \param[in] coord_force The scalar force for the pull coordinate. + * \param[in] mdatoms Atom properties, only masses are used. + * \param[in,out] forceWithVirial Force and virial buffers. */ -void apply_external_pull_coord_force(struct pull_t *pull, - int coord_index, - double coord_force, - const t_mdatoms *mdatoms, - rvec *force, - tensor virial); +void apply_external_pull_coord_force(struct pull_t *pull, + int coord_index, + double coord_force, + const t_mdatoms *mdatoms, + gmx::ForceWithVirial *forceWithVirial); /*! \brief Set the all the pull forces to zero. diff --git a/src/gromacs/random/seed.h b/src/gromacs/random/seed.h index d6ae6a4fbb..d6a7e27237 100644 --- a/src/gromacs/random/seed.h +++ b/src/gromacs/random/seed.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2015,2016, by the GROMACS development team, led by + * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -99,7 +99,8 @@ enum class RandomDomain Thermostat = 0x00005000, //!< Stochastic temperature coupling Barostat = 0x00006000, //!< Stochastic pressure coupling ReplicaExchange = 0x00007000, //!< Replica exchange metropolis moves - ExpandedEnsemble = 0x00008000 //!< Expanded ensemble lambda moves + ExpandedEnsemble = 0x00008000, //!< Expanded ensemble lambda moves + AwhBiasing = 0x00009000 //!< AWH biasing reference value moves }; } // namespace gmx diff --git a/src/gromacs/timing/wallcycle.cpp b/src/gromacs/timing/wallcycle.cpp index 3eef18da7f..8103a5fb86 100644 --- a/src/gromacs/timing/wallcycle.cpp +++ b/src/gromacs/timing/wallcycle.cpp @@ -108,7 +108,7 @@ static const char *wcn[ewcNR] = "PME wait for PP", "Wait + Recv. PME F", "Wait PME GPU spread", "Wait PME GPU gather", "Reduce GPU PME F", "Wait GPU NB nonloc.", "Wait GPU NB local", "NB X/F buffer ops.", - "Vsite spread", "COM pull force", + "Vsite spread", "COM pull force", "AWH", "Write traj.", "Update", "Constraints", "Comm. energies", "Enforced rotation", "Add rot. forces", "Position swapping", "IMD", "Test" }; diff --git a/src/gromacs/timing/wallcycle.h b/src/gromacs/timing/wallcycle.h index 4b7562af85..d4cf582d5f 100644 --- a/src/gromacs/timing/wallcycle.h +++ b/src/gromacs/timing/wallcycle.h @@ -55,7 +55,7 @@ enum { ewcPMEWAITCOMM, ewcPP_PMEWAITRECVF, ewcWAIT_GPU_PME_SPREAD, ewcWAIT_GPU_PME_GATHER, ewcPME_GPU_F_REDUCTION, ewcWAIT_GPU_NB_NL, ewcWAIT_GPU_NB_L, ewcNB_XF_BUF_OPS, - ewcVSITESPREAD, ewcPULLPOT, + ewcVSITESPREAD, ewcPULLPOT, ewcAWH, ewcTRAJ, ewcUPDATE, ewcCONSTR, ewcMoveE, ewcROT, ewcROTadd, ewcSWAP, ewcIMD, ewcTEST, ewcNR }; diff --git a/src/gromacs/utility/pleasecite.cpp b/src/gromacs/utility/pleasecite.cpp index 98c12342e1..2aad0cf457 100644 --- a/src/gromacs/utility/pleasecite.cpp +++ b/src/gromacs/utility/pleasecite.cpp @@ -376,6 +376,11 @@ void please_cite(FILE *fp, const char *key) "Quantifying Artifacts in Ewald Simulations of Inhomogeneous Systems with a Net Charge", "J. Chem. Theory Comput.", 10, 2014, "381-393" }, + { "Lindahl2014", + "V. Lindahl, J. Lidmar, B. Hess", + "Accelerated weight histogram method for exploring free energy landscapes", + "J. Chem. Phys.", + 141, 2014, "044110" }, }; #define NSTR (int)asize(citedb) diff --git a/src/programs/mdrun/md.cpp b/src/programs/mdrun/md.cpp index 0885f1e3ee..e90983ed78 100644 --- a/src/programs/mdrun/md.cpp +++ b/src/programs/mdrun/md.cpp @@ -50,6 +50,7 @@ #include "thread_mpi/threads.h" +#include "gromacs/awh/awh.h" #include "gromacs/commandline/filenm.h" #include "gromacs/domdec/domdec.h" #include "gromacs/domdec/domdec_network.h" @@ -92,6 +93,7 @@ #include "gromacs/mdlib/update.h" #include "gromacs/mdlib/vcm.h" #include "gromacs/mdlib/vsite.h" +#include "gromacs/mdtypes/awh-history.h" #include "gromacs/mdtypes/commrec.h" #include "gromacs/mdtypes/df_history.h" #include "gromacs/mdtypes/energyhistory.h" @@ -481,6 +483,10 @@ double gmx::do_md(FILE *fplog, t_commrec *cr, const gmx::MDLogger &mdlog, { gmx_fatal(FARGS, "Shell particles are not implemented with domain decomposition, use a single rank"); } + if (shellfc && ir->bDoAwh) + { + gmx_fatal(FARGS, "AWH biasing does not support shell particles."); + } if (inputrecDeform(ir)) { @@ -595,6 +601,23 @@ double gmx::do_md(FILE *fplog, t_commrec *cr, const gmx::MDLogger &mdlog, set_constraints(constr, top, ir, mdatoms, cr); } + /* Initialize AWH and restore state from history in checkpoint if needed. */ + if (ir->bDoAwh) + { + ir->awh = new gmx::Awh(fplog, *ir, cr, *ir->awhParams, opt2fn("-awh", nfile, fnm), ir->pull_work); + + if (startingFromCheckpoint) + { + /* Restore the AWH history read from checkpoint */ + ir->awh->restoreStateFromHistory(MASTER(cr) ? state_global->awhHistory.get() : nullptr); + } + else if (MASTER(cr)) + { + /* Initialize the AWH history here */ + state_global->awhHistory = ir->awh->initHistoryFromState(); + } + } + const bool useReplicaExchange = (replExParams.exchangeInterval > 0); if (useReplicaExchange && MASTER(cr)) { @@ -689,7 +712,7 @@ double gmx::do_md(FILE *fplog, t_commrec *cr, const gmx::MDLogger &mdlog, /* To minimize communication, compute_globals computes the COM velocity * and the kinetic energy for the velocities without COM motion removed. * Thus to get the kinetic energy without the COM contribution, we need - * to calls compute_globals twice. + * to call compute_globals twice. */ for (int cgloIteration = 0; cgloIteration < (bStopCM ? 2 : 1); cgloIteration++) { @@ -1177,6 +1200,19 @@ double gmx::do_md(FILE *fplog, t_commrec *cr, const gmx::MDLogger &mdlog, } else { + /* The AWH history need to be saved _before_ doing force calculations where the AWH bias is updated + (or the AWH update will be performed twice for one step when continuing). It would be best to + call this update function from do_md_trajectory_writing but that would occur after do_force. + One would have to divide the update_awh function into one function applying the AWH force + and one doing the AWH bias update. The update AWH bias function could then be called after + do_md_trajectory_writing (then containing update_awh_history). + The checkpointing will in the future probably moved to the start of the md loop which will + rid of this issue. */ + if (ir->bDoAwh && bCPT && MASTER(cr)) + { + ir->awh->updateHistory(state_global->awhHistory.get()); + } + /* The coordinates (x) are shifted (to get whole molecules) * in do_force. * This is parallellized as well, and does communication too. @@ -1948,6 +1984,11 @@ double gmx::do_md(FILE *fplog, t_commrec *cr, const gmx::MDLogger &mdlog, print_replica_exchange_statistics(fplog, repl_ex); } + if (ir->bDoAwh) + { + delete ir->awh; + } + // Clean up swapcoords if (ir->eSwapCoords != eswapNO) { diff --git a/src/programs/mdrun/runner.h b/src/programs/mdrun/runner.h index 3132c38771..d0db02bcf6 100644 --- a/src/programs/mdrun/runner.h +++ b/src/programs/mdrun/runner.h @@ -116,6 +116,7 @@ class Mdrunner { efLOG, "-rt", "rottorque", ffOPTWR }, { efMTX, "-mtx", "nm", ffOPTWR }, { efRND, "-multidir", nullptr, ffOPTRDMULT}, + { efXVG, "-awh", "awhinit", ffOPTRD }, { efDAT, "-membed", "membed", ffOPTRD }, { efTOP, "-mp", "membed", ffOPTRD }, { efNDX, "-mn", "membed", ffOPTRD }, -- 2.11.4.GIT