2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2019, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements force provider for density fitting
39 * \author Christian Blau <blau@kth.se>
40 * \ingroup module_applied_forces
44 #include "densityfittingforceprovider.h"
48 #include "gromacs/compat/optional.h"
49 #include "gromacs/gmxlib/network.h"
50 #include "gromacs/math/densityfit.h"
51 #include "gromacs/math/densityfittingforce.h"
52 #include "gromacs/math/exponentialmovingaverage.h"
53 #include "gromacs/math/gausstransform.h"
54 #include "gromacs/mdtypes/commrec.h"
55 #include "gromacs/mdtypes/enerdata.h"
56 #include "gromacs/mdtypes/forceoutput.h"
57 #include "gromacs/mdtypes/iforceprovider.h"
58 #include "gromacs/pbcutil/pbc.h"
60 #include "densityfittingamplitudelookup.h"
61 #include "densityfittingparameters.h"
69 /*! \internal \brief Generate the spread kernel from Gaussian parameters.
71 * \param[in] sigma the width of the Gaussian to be spread
72 * \param[in] nSigma the range of the Gaussian in multiples of sigma
73 * \param[in] scaleToLattice the coordinate transformation into the spreading lattice
74 * \returns A Gauss-transform kernel shape
76 GaussianSpreadKernelParameters::Shape makeSpreadKernel(real sigma, real nSigma, const ScaleCoordinates& scaleToLattice)
78 RVec sigmaInLatticeCoordinates{ sigma, sigma, sigma };
79 scaleToLattice({ &sigmaInLatticeCoordinates, &sigmaInLatticeCoordinates + 1 });
80 return { DVec{ sigmaInLatticeCoordinates[XX], sigmaInLatticeCoordinates[YY],
81 sigmaInLatticeCoordinates[ZZ] },
87 /********************************************************************
88 * DensityFittingForceProvider::Impl
91 class DensityFittingForceProvider::Impl
94 //! \copydoc DensityFittingForceProvider(const DensityFittingParameters ¶meters)
95 Impl(const DensityFittingParameters& parameters,
96 basic_mdspan<const float, dynamicExtents3D> referenceDensity,
97 const TranslateAndScale& transformationToDensityLattice,
98 const LocalAtomSet& localAtomSet,
100 double simulationTimeStep,
101 const DensityFittingForceProviderState& state);
103 void calculateForces(const ForceProviderInput& forceProviderInput,
104 ForceProviderOutput* forceProviderOutput);
106 DensityFittingForceProviderState state();
109 const DensityFittingParameters& parameters_;
110 DensityFittingForceProviderState state_;
111 LocalAtomSet localAtomSet_;
113 GaussianSpreadKernelParameters::Shape spreadKernel_;
114 GaussTransform3D gaussTransform_;
115 DensitySimilarityMeasure measure_;
116 DensityFittingForce densityFittingForce_;
117 //! the local atom coordinates transformed into the grid coordinate system
118 std::vector<RVec> transformedCoordinates_;
119 std::vector<RVec> forces_;
120 DensityFittingAmplitudeLookup amplitudeLookup_;
121 TranslateAndScale transformationToDensityLattice_;
122 RVec referenceDensityCenter_;
125 //! Optionally scale the force according to a moving average of the similarity
126 compat::optional<ExponentialMovingAverage> expAverageSimilarity_;
129 DensityFittingForceProvider::Impl::~Impl() = default;
131 DensityFittingForceProvider::Impl::Impl(const DensityFittingParameters& parameters,
132 basic_mdspan<const float, dynamicExtents3D> referenceDensity,
133 const TranslateAndScale& transformationToDensityLattice,
134 const LocalAtomSet& localAtomSet,
136 double simulationTimeStep,
137 const DensityFittingForceProviderState& state) :
138 parameters_(parameters),
140 localAtomSet_(localAtomSet),
141 spreadKernel_(makeSpreadKernel(parameters_.gaussianTransformSpreadingWidth_,
142 parameters_.gaussianTransformSpreadingRangeInMultiplesOfWidth_,
143 transformationToDensityLattice.scaleOperationOnly())),
144 gaussTransform_(referenceDensity.extents(), spreadKernel_),
145 measure_(parameters.similarityMeasureMethod_, referenceDensity),
146 densityFittingForce_(spreadKernel_),
147 transformedCoordinates_(localAtomSet_.numAtomsLocal()),
148 amplitudeLookup_(parameters_.amplitudeLookupMethod_),
149 transformationToDensityLattice_(transformationToDensityLattice),
151 expAverageSimilarity_(compat::nullopt)
153 if (parameters_.adaptiveForceScaling_)
155 GMX_ASSERT(simulationTimeStep > 0,
156 "Simulation time step must be larger than zero for adaptive for scaling.");
157 expAverageSimilarity_.emplace(ExponentialMovingAverage(
158 parameters_.adaptiveForceScalingTimeConstant_
159 / (simulationTimeStep * parameters_.calculationIntervalInSteps_),
160 state.exponentialMovingAverageState_));
162 referenceDensityCenter_ = { real(referenceDensity.extent(XX)) / 2,
163 real(referenceDensity.extent(YY)) / 2,
164 real(referenceDensity.extent(ZZ)) / 2 };
165 transformationToDensityLattice_.scaleOperationOnly().inverseIgnoringZeroScale(
166 { &referenceDensityCenter_, &referenceDensityCenter_ + 1 });
167 // correct the reference density center for a shift
168 // if the reference density does not have its origin at (0,0,0)
169 RVec referenceDensityOriginShift(0, 0, 0);
170 transformationToDensityLattice_({ &referenceDensityOriginShift, &referenceDensityOriginShift + 1 });
171 transformationToDensityLattice_.scaleOperationOnly().inverseIgnoringZeroScale(
172 { &referenceDensityOriginShift, &referenceDensityOriginShift + 1 });
173 referenceDensityCenter_ -= referenceDensityOriginShift;
176 void DensityFittingForceProvider::Impl::calculateForces(const ForceProviderInput& forceProviderInput,
177 ForceProviderOutput* forceProviderOutput)
179 // do nothing but count number of steps when not in density fitting step
180 if (state_.stepsSinceLastCalculation_ % parameters_.calculationIntervalInSteps_ != 0)
182 ++(state_.stepsSinceLastCalculation_);
186 state_.stepsSinceLastCalculation_ = 1;
188 // do nothing if there are no density fitting atoms on this node
189 if (localAtomSet_.numAtomsLocal() == 0)
193 transformedCoordinates_.resize(localAtomSet_.numAtomsLocal());
194 // pick and copy atom coordinates
195 std::transform(std::cbegin(localAtomSet_.localIndex()), std::cend(localAtomSet_.localIndex()),
196 std::begin(transformedCoordinates_),
197 [&forceProviderInput](int index) { return forceProviderInput.x_[index]; });
199 // pick periodic image that is closest to the center of the reference density
202 set_pbc(&pbc, pbcType_, forceProviderInput.box_);
203 for (RVec& x : transformedCoordinates_)
206 pbc_dx(&pbc, x, referenceDensityCenter_, dx);
207 x = referenceDensityCenter_ + dx;
211 // transform local atom coordinates to density grid coordinates
212 transformationToDensityLattice_(transformedCoordinates_);
214 // spread atoms on grid
215 gaussTransform_.setZero();
217 std::vector<real> amplitudes =
218 amplitudeLookup_(forceProviderInput.mdatoms_, localAtomSet_.localIndex());
220 if (parameters_.normalizeDensities_)
222 real sum = std::accumulate(std::begin(amplitudes), std::end(amplitudes), 0.);
223 if (PAR(&forceProviderInput.cr_))
225 gmx_sum(1, &sum, &forceProviderInput.cr_);
227 for (real& amplitude : amplitudes)
233 auto amplitudeIterator = amplitudes.cbegin();
235 for (const auto& r : transformedCoordinates_)
237 gaussTransform_.add({ r, *amplitudeIterator });
242 if (PAR(&forceProviderInput.cr_))
244 // \todo update to real once GaussTransform class returns real
245 gmx_sumf(gaussTransform_.view().mapping().required_span_size(),
246 gaussTransform_.view().data(), &forceProviderInput.cr_);
249 // calculate grid derivative
250 const DensitySimilarityMeasure::density& densityDerivative =
251 measure_.gradient(gaussTransform_.constView());
253 forces_.resize(localAtomSet_.numAtomsLocal());
255 std::begin(transformedCoordinates_), std::end(transformedCoordinates_), std::begin(amplitudes),
256 std::begin(forces_), [&densityDerivative, this](const RVec r, real amplitude) {
257 return densityFittingForce_.evaluateForce({ r, amplitude }, densityDerivative);
260 transformationToDensityLattice_.scaleOperationOnly().inverseIgnoringZeroScale(forces_);
262 auto densityForceIterator = forces_.cbegin();
263 const real effectiveForceConstant = state_.adaptiveForceConstantScale_ * parameters_.calculationIntervalInSteps_
264 * parameters_.forceConstant_;
265 for (const auto localAtomIndex : localAtomSet_.localIndex())
267 forceProviderOutput->forceWithVirial_.force_[localAtomIndex] +=
268 effectiveForceConstant * *densityForceIterator;
269 ++densityForceIterator;
272 // calculate corresponding potential energy
273 const float similarity = measure_.similarity(gaussTransform_.constView());
274 const real energy = -similarity * parameters_.forceConstant_ * state_.adaptiveForceConstantScale_;
275 forceProviderOutput->enerd_.term[F_DENSITYFITTING] += energy;
277 if (expAverageSimilarity_.has_value())
279 expAverageSimilarity_->updateWithDataPoint(similarity);
281 if (expAverageSimilarity_->increasing())
283 state_.adaptiveForceConstantScale_ /= 1._real + expAverageSimilarity_->inverseTimeConstant();
287 state_.adaptiveForceConstantScale_ *= 1._real + expAverageSimilarity_->inverseTimeConstant();
292 DensityFittingForceProviderState DensityFittingForceProvider::Impl::state()
294 if (expAverageSimilarity_.has_value())
296 state_.exponentialMovingAverageState_ = expAverageSimilarity_->state();
301 /********************************************************************
302 * DensityFittingForceProvider
305 DensityFittingForceProvider::~DensityFittingForceProvider() = default;
307 DensityFittingForceProvider::DensityFittingForceProvider(const DensityFittingParameters& parameters,
308 basic_mdspan<const float, dynamicExtents3D> referenceDensity,
309 const TranslateAndScale& transformationToDensityLattice,
310 const LocalAtomSet& localAtomSet,
312 double simulationTimeStep,
313 const DensityFittingForceProviderState& state) :
314 impl_(new Impl(parameters, referenceDensity, transformationToDensityLattice, localAtomSet, pbcType, simulationTimeStep, state))
318 void DensityFittingForceProvider::calculateForces(const ForceProviderInput& forceProviderInput,
319 ForceProviderOutput* forceProviderOutput)
321 impl_->calculateForces(forceProviderInput, forceProviderOutput);
324 DensityFittingForceProviderState DensityFittingForceProvider::state()
326 return impl_->state();