Add replacements for pbc enumerations
[gromacs.git] / src / gromacs / math / gausstransform.cpp
blob55d62764a5defa2a5eb3fcf49f54c5cb8d32fcc4
1 /*
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.
35 /*! \internal \file
36 * \brief
37 * Implements Gaussian function evaluations on lattices and related functionality
39 * \author Christian Blau <blau@kth.se>
41 * \ingroup module_math
43 #include "gmxpre.h"
45 #include "gausstransform.h"
47 #include <cmath>
49 #include <algorithm>
50 #include <array>
52 #include "gromacs/math/functions.h"
53 #include "gromacs/math/utilities.h"
55 #include "multidimarray.h"
57 namespace gmx
60 /********************************************************************
61 * GaussianOn1DLattice::Impl
64 class GaussianOn1DLattice::Impl
66 public:
67 Impl(int numGridPointsForSpreadingHalfWidth, real sigma);
68 ~Impl() = default;
69 Impl(const Impl &other) = default;
70 Impl &operator=(const Impl &other) = default;
72 /*! \brief evaluate Gaussian function at all lattice points
73 * \param[in] amplitude the amplitude of the Gaussian
74 * \param[in] dx distance from the center
76 void spread(double amplitude, real dx);
77 //! Largest distance in number of gridpoints from 0
78 int numGridPointsForSpreadingHalfWidth_;
79 /*! \brief Avoid overflow for E2^offset and underflow for E3(i).
81 * Occurs when sigma is much smaller than numGridPointsForSpreadingHalfWidth_.
83 * E2^offset smaller than maximum float requires
84 * \f$exp(dx / (2*square(sigma))^numGridPointsForSpreadingHalfWidth_ \leq max_float \f$
85 * The maximum expected distance of the Gaussian center to the next lattice point is dx = 0.5,
86 * thus the maximum spread distance here is \f$4 * sigma^2 * \log(\mathrm{maxfloat})\f$ .
88 * E3(i) larger than minmium float requires
89 * exp(i^2 / 2*(sigma)^2) > min_float
90 * Thus the maximum spread distance here is \f$\sigma \sqrt(-2\log(\mathrm{minfloat}))\f$
92 int maxEvaluatedSpreadDistance_;
93 //! Width of the Gaussian function
94 double sigma_;
95 //! The result of the spreading calculation
96 std::vector<float> spreadingResult_;
97 //! Pre-calculated exp(-gridIndex^2/2 * (sigma^2)) named as in Greengard2004
98 std::vector<float> e3_;
99 /*! \brief Equal to std::floor(std::log(std::numeric_limits<float>::max())).
100 * Above expression is not constexpr and a const variable would implicitly delete default copy assignment.
101 * Therefore resorting to setting number manually.
103 static constexpr double c_logMaxFloat = 88.72284;
104 static constexpr double c_logMinFloat = -87.33654;
107 GaussianOn1DLattice::Impl::Impl(int numGridPointsForSpreadingHalfWidth, real sigma) :
108 numGridPointsForSpreadingHalfWidth_(numGridPointsForSpreadingHalfWidth),
109 sigma_(sigma),
110 spreadingResult_(2 * numGridPointsForSpreadingHalfWidth + 1)
112 maxEvaluatedSpreadDistance_ = std::min(numGridPointsForSpreadingHalfWidth_, static_cast<int>(std::floor(4 * square(sigma) * c_logMaxFloat)) - 1);
113 maxEvaluatedSpreadDistance_ = std::min(maxEvaluatedSpreadDistance_, static_cast<int>(std::floor(sigma * sqrt(-2.0 * c_logMinFloat))) - 1);
115 std::generate_n(std::back_inserter(e3_), maxEvaluatedSpreadDistance_ + 1,
116 [sigma, latticeIndex = 0]() mutable {
117 return std::exp(-0.5 * square(latticeIndex++ / sigma));
120 std::fill(std::begin(spreadingResult_), std::end(spreadingResult_), 0.);
123 void GaussianOn1DLattice::Impl::spread(double amplitude, real dx)
125 /* The spreading routine implements the fast gaussian gridding as in
127 * Leslie Greengard and June-Yub Lee,
128 * "Accelerating the Nonuniform Fast Fourier Transform"
129 * SIAM REV 2004 Vol. 46, No. 3, pp. 443-454 DOI. 10.1137/S003614450343200X
131 * Following the naming conventions for e1, e2 and e3, nu = 1, m = numGridPointsForSpreadingHalfWidth_.
133 * Speed up is achieved by factorization of the exponential that is evaluted
134 * at regular lattice points i, where the distance from the
135 * Gaussian center is \f$x-i\f$:
137 * \f[
138 * a * \exp(-(x^2-2*i*x+ i^2)/(2*\sigma^2)) =
139 * a * \exp(-x^2/2*\sigma^2) * \exp(x/\sigma^2)^i * \exp(i/2*sigma^2) =
140 * e_1(x) * e_2(x)^i * e_3(i)
141 * \f]
143 * Requiring only two exp evaluations per spreading operation.
146 const double e1 = amplitude * exp(-0.5 * dx * dx / square(sigma_)) / (sqrt(2 * M_PI) * sigma_);
147 spreadingResult_[numGridPointsForSpreadingHalfWidth_] = e1;
149 const double e2 = exp(dx / square(sigma_));
151 double e2pow = e2; //< powers of e2, e2^offset
153 // Move outwards from mid-point, using e2pow value for both points simultaneously
154 // o o o<----O---->o o o
155 for (int offset = 1; offset < maxEvaluatedSpreadDistance_; offset++)
157 const double e1_3 = e1 * e3_[offset];
158 spreadingResult_[numGridPointsForSpreadingHalfWidth_ + offset] = e1_3 * e2pow;
159 spreadingResult_[numGridPointsForSpreadingHalfWidth_ - offset] = e1_3 / e2pow;
160 e2pow *= e2;
162 // separate statement for gridpoints at the end of the range avoids
163 // overflow for large sigma and saves one e2 multiplication operation
164 spreadingResult_[numGridPointsForSpreadingHalfWidth_ - maxEvaluatedSpreadDistance_] = (e1 / e2pow) * e3_[maxEvaluatedSpreadDistance_];
165 spreadingResult_[numGridPointsForSpreadingHalfWidth_ + maxEvaluatedSpreadDistance_] = (e1 * e2pow) * e3_[maxEvaluatedSpreadDistance_];
168 /********************************************************************
169 * GaussianOn1DLattice
172 GaussianOn1DLattice::GaussianOn1DLattice(int numGridPointsForSpreadingHalfWidth_, real sigma) : impl_(new Impl(numGridPointsForSpreadingHalfWidth_, sigma))
176 GaussianOn1DLattice::~GaussianOn1DLattice () {}
178 void GaussianOn1DLattice::spread(double amplitude, real dx)
180 impl_->spread(amplitude, dx);
183 ArrayRef<const float> GaussianOn1DLattice::view()
185 return impl_->spreadingResult_;
188 GaussianOn1DLattice::GaussianOn1DLattice(const GaussianOn1DLattice &other)
189 : impl_(new Impl(*other.impl_))
193 GaussianOn1DLattice &GaussianOn1DLattice::operator=(const GaussianOn1DLattice &other)
195 *impl_ = *other.impl_;
196 return *this;
199 GaussianOn1DLattice::GaussianOn1DLattice(GaussianOn1DLattice &&) noexcept = default;
201 GaussianOn1DLattice &GaussianOn1DLattice::operator=(GaussianOn1DLattice &&) noexcept = default;
203 namespace
206 //! rounds real-valued coordinate to the closest integer values
207 IVec closestIntegerPoint(const RVec &coordinate)
209 return {
210 roundToInt(coordinate[XX]),
211 roundToInt(coordinate[YY]),
212 roundToInt(coordinate[ZZ])
216 /*! \brief Substracts a range from a three-dimensional integer coordinate and ensures
217 * the resulting coordinate is within a lattice.
218 * \param[in] index point in lattice
219 * \param[in] range to be shifted
220 * \returns Shifted index or zero if shifted index is smaller than zero.
222 IVec rangeBeginWithinLattice(const IVec &index, const IVec &range)
224 return elementWiseMax({0, 0, 0}, index - range);
227 /*! \brief Adds a range from a three-dimensional integer coordinate and ensures
228 * the resulting coordinate is within a lattice.
229 * \param[in] index point in lattice
230 * \param[in] extents extent of the lattice
231 * \param[in] range to be shifted
232 * \returns Shifted index or the lattice extent if shifted index is larger than the extent
234 IVec rangeEndWithinLattice(const IVec &index, const dynamicExtents3D &extents, const IVec &range)
236 IVec extentAsIvec(static_cast<int>(extents.extent(XX)), static_cast<int>(extents.extent(YY)), static_cast<int>(extents.extent(ZZ)));
237 return elementWiseMin(extentAsIvec, index + range);
241 } // namespace
243 /********************************************************************
244 * OuterProductEvaluator
247 mdspan<const float, dynamic_extent, dynamic_extent>
248 OuterProductEvaluator::operator()(ArrayRef<const float> x, ArrayRef<const float> y)
250 data_.resize(ssize(x), ssize(y));
251 for (int xIndex = 0; xIndex < ssize(x); ++xIndex)
253 const auto xValue = x[xIndex];
254 std::transform(std::begin(y), std::end(y), begin(data_.asView()[xIndex]),
255 [xValue](float yValue) { return xValue * yValue; });
257 return data_.asConstView();
260 /********************************************************************
261 * IntegerBox
264 IntegerBox::IntegerBox(const IVec &begin, const IVec &end) : begin_ {begin}, end_ {
269 const IVec &IntegerBox::begin() const{return begin_; }
270 const IVec &IntegerBox::end() const { return end_; }
272 bool IntegerBox::empty() const { return !((begin_[XX] < end_[XX] ) && (begin_[YY] < end_[YY]) && (begin_[ZZ] < end_[ZZ])); }
274 IntegerBox spreadRangeWithinLattice(const IVec &center, dynamicExtents3D extent, IVec range)
276 const IVec begin = rangeBeginWithinLattice(center, range);
277 const IVec end = rangeEndWithinLattice(center, extent, range);
278 return {begin, end};
280 /********************************************************************
281 * GaussianSpreadKernel
284 IVec GaussianSpreadKernelParameters::Shape::latticeSpreadRange() const
286 DVec range(std::ceil(sigma_[XX] * spreadWidthMultiplesOfSigma_), std::ceil(sigma_[YY] * spreadWidthMultiplesOfSigma_), std::ceil(sigma_[ZZ] * spreadWidthMultiplesOfSigma_));
287 return range.toIVec();
290 /********************************************************************
291 * GaussTransform3D::Impl
294 /*! \internal \brief
295 * Private implementation class for GaussTransform3D.
297 class GaussTransform3D::Impl
299 public:
300 //! Construct from extent and spreading width and range
301 Impl(const dynamicExtents3D &extent,
302 const GaussianSpreadKernelParameters::Shape &kernelShapeParameters);
303 ~Impl() = default;
304 //! Copy constructor
305 Impl(const Impl &other) = default;
306 //! Copy assignment
307 Impl &operator=(const Impl &other) = default;
308 //! Add another gaussian
309 void add(const GaussianSpreadKernelParameters::PositionAndAmplitude &localParamters );
310 //! The width of the Gaussian in lattice spacing units
311 BasicVector<double> sigma_;
312 //! The spread range in lattice points
313 IVec spreadRange_;
314 //! The result of the Gauss transform
315 MultiDimArray<std::vector<float>, dynamicExtents3D> data_;
316 //! The outer product of a Gaussian along the z and y dimension
317 OuterProductEvaluator outerProductZY_;
318 //! The three one-dimensional Gaussians, whose outer product is added to the Gauss transform
319 std::array<GaussianOn1DLattice, DIM> gauss1d_;
322 GaussTransform3D::Impl::Impl(const dynamicExtents3D &extent,
323 const GaussianSpreadKernelParameters::Shape &kernelShapeParameters)
324 : sigma_ {kernelShapeParameters.sigma_ },
325 spreadRange_ {
326 kernelShapeParameters.latticeSpreadRange()
328 data_ {
329 extent
331 gauss1d_( {GaussianOn1DLattice(spreadRange_[XX], sigma_[XX]),
332 GaussianOn1DLattice(spreadRange_[YY], sigma_[YY]),
333 GaussianOn1DLattice(spreadRange_[ZZ], sigma_[ZZ]) } )
337 void GaussTransform3D::Impl::add(const GaussianSpreadKernelParameters::PositionAndAmplitude &localParameters)
339 const IVec closestLatticePoint = closestIntegerPoint(localParameters.coordinate_);
340 const auto spreadRange = spreadRangeWithinLattice(closestLatticePoint, data_.asView().extents(), spreadRange_);
342 // do nothing if the added Gaussian will never reach the lattice
343 if (spreadRange.empty())
345 return;
348 for (int dimension = XX; dimension <= ZZ; ++dimension)
350 // multiply with amplitude so that Gauss3D = (amplitude * Gauss_x) * Gauss_y * Gauss_z
351 const float gauss1DAmplitude = dimension > XX ? 1.0 : localParameters.amplitude_;
352 gauss1d_[dimension].spread(gauss1DAmplitude, localParameters.coordinate_[dimension] - closestLatticePoint[dimension]);
355 const auto spreadZY = outerProductZY_(gauss1d_[ZZ].view(), gauss1d_[YY].view());
356 const auto spreadX = gauss1d_[XX].view();
357 const IVec spreadGridOffset = spreadRange_ - closestLatticePoint;
359 // \todo optimize these loops if performance critical
360 // The looping strategy uses that the last, x-dimension is contiguous in the memory layout
361 for (int zLatticeIndex = spreadRange.begin()[ZZ]; zLatticeIndex < spreadRange.end()[ZZ]; ++zLatticeIndex)
363 const auto zSlice = data_.asView()[zLatticeIndex];
365 for (int yLatticeIndex = spreadRange.begin()[YY]; yLatticeIndex < spreadRange.end()[YY]; ++yLatticeIndex)
367 const auto ySlice = zSlice[yLatticeIndex];
368 const float zyPrefactor = spreadZY(zLatticeIndex + spreadGridOffset[ZZ], yLatticeIndex + spreadGridOffset[YY]);
370 for (int xLatticeIndex = spreadRange.begin()[XX]; xLatticeIndex < spreadRange.end()[XX]; ++xLatticeIndex)
372 const float xPrefactor = spreadX[xLatticeIndex + spreadGridOffset[XX]];
373 ySlice[xLatticeIndex] += zyPrefactor * xPrefactor;
379 /********************************************************************
380 * GaussTransform3D
383 GaussTransform3D::GaussTransform3D(const dynamicExtents3D &extent,
384 const GaussianSpreadKernelParameters::Shape &kernelShapeParameters) : impl_(new Impl(extent, kernelShapeParameters))
388 void GaussTransform3D::add(const GaussianSpreadKernelParameters::PositionAndAmplitude &localParameters)
390 impl_->add(localParameters);
393 void GaussTransform3D::setZero()
395 std::fill(begin(impl_->data_), end(impl_->data_), 0.);
398 const basic_mdspan<const float, dynamicExtents3D> GaussTransform3D::view()
400 return impl_->data_.asConstView();
403 GaussTransform3D::~GaussTransform3D()
406 GaussTransform3D::GaussTransform3D(const GaussTransform3D &other)
407 : impl_(new Impl(*other.impl_))
411 GaussTransform3D &GaussTransform3D::operator=(const GaussTransform3D &other)
413 *impl_ = *other.impl_;
414 return *this;
417 GaussTransform3D::GaussTransform3D(GaussTransform3D &&) noexcept = default;
419 GaussTransform3D &GaussTransform3D::operator=(GaussTransform3D &&) noexcept = default;
421 } // namespace gmx