Unverified Commit f55abcaa authored by Evan Pretti's avatar Evan Pretti Committed by GitHub
Browse files

Add constant potential method (#4870)



* Initial implementation of C++ API

* Add kernel interface and information for API generation

* API updates for updating electrode parameters

* Add serialization proxy for ConstantPotentialForce

* Update file headers

* Add CG error tolerance and fix units on getCharges() return value

* Initial implementation of matrix solver

* Fixes and conjugate gradient solver

* Try to fix Linux and Windows builds

* Make sure charge constraint target is on total charge

* Restore handling of exceptions like NonbondedForce since they won't involve electrode atoms

* Ameliorate numerical instability in constrained conjugate gradient

* Fix uninitialized pointers, memory leak, and style

* Set CG tolerance units in Python API

* Test ConstantPotentialForce serialization

* Read/write ExceptionsUsePeriodicBoundaryConditions as bool

* Improve constrained conjugate gradient robustness to roundoff error accumulation

* Recompute matrix if electrode atoms move due to setPositions()

* Tolerance is now in gradient (potential) units again

* Add neutralizing background correction

* Add Python API tests

* Fixes for CG and nonbonded exceptions

* Add initial tests checking against existing NonbondedForce behavior

* Expand test suite and fix some implementation issues

* Add additional tests using larger reference system

* Add Gaussian test

* Finish test against reference computation

* CPU platform implementation

* Fixes for compilation on some platforms

* Fixes for constant potential with AVX/AVX2

* Test linking CPU PME library to constant potential test directly

* Older SWIG versions don't support Python set to C++ set conversion

* Add user guide entry

* Increase speed of reference test

* Conditional building constant potential CPU test is unreliable

* Debugging

* Miscellaneous fixes and improvements for CI

* Cache charges so solver will not run if system and coordinates have not changed

* Preconditioner flag, stability, and automatic detection improvements

* Add GPU platform-specific constant potential kernel classes

* PME and device-host I/O changes to support constant potential

* Initial common constant potential implementation

* Constant potential fixes:

* Fix preconditioner PME position/charge save/restore logic

* Fix reduction synchronization in constant potential solver kernels

* Add double-float accumulation for conjugate gradient solver when
  double unsupported by hardware

* Improve conditioning of a test system, and make sure particles are in or
out of cutoff for consistency and ease of comparing between platforms

* Reorder guess charges for CG when atom reordering changes positions

* Remove PME queue for now

* Trying to debug optimized direct space derivative kernel

* Remove extraneous debugging lines

* Style updates; just make CPU preconditioner double precision

* Debugging updated optimized direct derivatives kernel for all but OpenCL CPU

* OpenCL CPU implementation of direct space derivatives, and cleanup

* Try to make test even shorter to not time out on CI

* Temporary - Debugging

* Debugging

* Debugging

* Debugging

* Debugging

* Remove debugging code and fix reduction synchronization

* Fix other reductions

* Debugging - are tests hanging or just slow on CI?

* Debugging

* Debugging

* Fix macro for case when double precision is available on hardware

* Remove changes for debugging again

* Try to improve matrix solver cache locality by uploading transpose

* Fixes for atom ordering and periodic images

* Can't rely on reorder listener for cell offset updates

* Test reducing number of contexts and timing for CI

* Debugging

* Remove timing code and revert debugging changes

* Matrix solver and plasma term optimizations

* Reduce CG solver kernel calls and downloads

* Don't read back convergence flag from global memory

* Update PME due to refactoring in master branch

* Faster matrix solver (1st step)

* Faster matrix solver for CUDA

* Faster matrix solver compatibility with non-CUDA platforms

* Matrix solver fixes

* Use warp shuffle reductions when possible

* Attempt to work around intermittent compiler crash in Intel CPU OpenCL

* Optimize CG solver kernel 1

* Rework CG solver so some kernels can use more than 1 block

* Don't run out of shared memory

* Asynchronously download convergence flag while clearing buffers

---------
Co-authored-by: default avatarEvan Pretti <pretti@sh03-17n15.int>
parent 0ad62341
......@@ -463,6 +463,9 @@ ENDIF (EXECUTABLE_OUTPUT_PATH)
ADD_SUBDIRECTORY(docs-source)
IF(BUILD_TESTING)
ADD_SUBDIRECTORY(tests)
IF(OPENMM_BUILD_CPU_LIB AND OPENMM_BUILD_CPU_TESTS AND (NOT OPENMM_BUILD_PME_PLUGIN))
MESSAGE(SEND_ERROR "The CPU platform tests require that the CPU PME plugin be built.")
ENDIF(OPENMM_BUILD_CPU_LIB AND OPENMM_BUILD_CPU_TESTS AND (NOT OPENMM_BUILD_PME_PLUGIN))
ENDIF(BUILD_TESTING)
SET(OPENMM_BUILD_EXAMPLES ON CACHE BOOL "Build example executables")
......
......@@ -7,7 +7,7 @@
set -euxo pipefail
# This is the image for PowerPC + CUDA
export DOCKER_IMAGE="quay.io/condaforge/linux-anvil-ppc64le-cuda:10.2"
export DOCKER_IMAGE="quay.io/condaforge/linux-anvil-ppc64le-cuda:11.8"
# # Use this other one for ARM debugging
# export DOCKER_IMAGE="quay.io/condaforge/linux-anvil-aarch64"
......@@ -17,7 +17,7 @@ export COMPILERS="compilers"
# export COMPILERS="devtoolset-7"
# Choose your Python version
export PYTHON_VER="3.9"
export PYTHON_VER="3.13"
# Number of CPUs to use
export CPU_COUNT=2
......@@ -51,4 +51,4 @@ docker run \
# Once you are inside the Docker session, you can use this to reproduce the CI steps:
#
# bash /home/conda/workspace/devtools/ci/gh-actions/scripts/run_steps_inside_docker_image.sh
\ No newline at end of file
# bash /home/conda/workspace/devtools/ci/gh-actions/scripts/run_steps_inside_docker_image.sh
......@@ -24,6 +24,7 @@ These classes implement forces that are widely used in biomolecular simulation.
.. toctree::
:maxdepth: 2
generated/ConstantPotentialForce
generated/CMAPTorsionForce
generated/DrudeForce
generated/GBSAOBCForce
......
......@@ -125,6 +125,16 @@
type = {Journal Article}
}
@article{Dufils2019
author = {Dufils, Thomas and Jeanmairet, Guillaume and Rotenberg, Benjamin and Sprik, Michiel and Salanne, Mathieu},
title = {Simulating electrochemical systems by combining the finite field method with a constant potential electrode},
journal = {Physical Review Letters},
volume = {123},
pages = {195501},
year = {2019},
type = {Journal Article}
}
@article{Espanol1995
author = {Español, P. and Warren, P.},
title = {Statistical Mechanics of Dissipative Particle Dynamics},
......@@ -490,6 +500,16 @@
type = {Journal Article}
}
@article{Scalfi2020
author = {Scalfi, Laura and Dufils, Thomas and Reeves, Kyle G. and Rotenberg, Benjamin and Salanne, Mathieu},
title = {A semiclassical Thomas-Fermi model to tune the metallicity of electrodes in molecular simulations},
journal = {Journal of Chemical Physics},
volume = {153},
pages = {174704},
year = {2020},
type = {Journal Article}
}
@article{Schaefer1998
author = {Schaefer, Michael and Bartels, Christian and Karplus, Martin},
title = {Solution conformations and thermodynamics of structured peptides: molecular dynamics simulation with an implicit solvation model},
......
......@@ -321,6 +321,8 @@ systems and parameter values, but no guarantees are made. It is important to
validate your own simulations, and identify parameter values that produce
acceptable accuracy for each system.
.. _coulomb-interaction-pme:
Coulomb Interaction With Particle Mesh Ewald
============================================
......@@ -460,6 +462,101 @@ where :math:`r_i` is the atomic radius of particle *i*\ , :math:`r_i` is
its atomic radius, and :math:`r_\mathit{solvent}` is the solvent radius, which is taken
to be 0.14 nm. The default value for the energy scale :math:`E_{SA}` is 2.25936 kJ/mol/nm\ :sup:`2`\ .
ConstantPotentialForce
**********************
This force is an implementation of the periodic finite field constant potential
method :cite:`Dufils2019`. The constant potential method implements Coulomb
interactions between particles using the particle mesh Ewald method as described
in section :numref:`coulomb-interaction-pme`. Unlike Coulomb interactions
implemented by a NonbondedForce that use fixed charges, the constant potential
method allows certain particles in a system to be designated electrode particles
whose charges can fluctuate. Additionally, instead of point charges, these
electrode particles have Gaussian charge densities, are held at given electric
potentials, and can optionally employ a simple Thomas-Fermi metallicity model
:cite:`Scalfi2020`. Finally, an electric field can be applied to all charged
particles in the system. For a given configuration of all particles, the
charges on electrode particles are set to minimize the total electrostatic
energy, given by
.. math::
E=E_{PME}+E_{gauss}+E_{self}+E_{field}+E_{potential}+E_{TF}
Here :math:`E_{PME}` is the interaction of point particles computed by the
particle mesh Ewald method. Now
.. math::
E_{gauss}=-\frac{1}{2}\sum_{i,j}q_iq_i\frac{\text{erfc}\left(\eta_{ij}r_{ij}\right)}{r_{ij}}
with :math:`\eta_{ij}=1/\sqrt{w_i^2+w_j^2}`, where :math:`w_i=0` for a
non-electrode (point) particle and :math:`w_i>0` for an electrode particle. No
term is included in the sum when :math:`i=j` or :math:`w_i=w_j=0`. Note that in
OpenMM's implementation, specification of an electrode requires the Gaussian
width :math:`w_i`, while the reciprocal width :math:`\eta_i=1/w_i` is often used
in the literature. In addition to this correction to interactions between
particles, the presence of Gaussian charge densities on electrode particles
contributes a self-interaction energy:
.. math::
E_{self}=\frac{1}{\sqrt{2\pi}}\sum_{i\in\text{elec}}\frac{q_i^2}{w_i}
Next, for the electric field :math:`\mathbf{E}` (on all particles) and applied
potential (:math:`\Psi_i` on electrode particles :math:`i`),
.. math::
E_{field}=-\sum_{i}q_i\mathbf{r}_i\cdot\mathbf{E}
.. math::
E_{potential}=-\sum_{i\in\text{elec}}q_i\Psi_i
Finally, for the Thomas-Fermi contribution on the electrode particles,
.. math::
E_{TF}=2\pi\sum_{i\in\text{elec}}q_i^2\left(\frac{\ell_{TF,i}^2}{v_{TF,i}}\right)
where :math:`\ell_{TF,i}` is the Thomas-Fermi length and :math:`v_{TF,i}` is a
characteristic volume scale (often referred to as a "Voronoi volume" in other
implementations). In OpenMM's implementation, the parameter
:math:`\ell_{TF,i}^2/v_{TF,i}` is specified as a single Thomas-Fermi parameter,
with units of reciprocal length, for all particles in an electrode.
Besides these additional terms, and the fluctuating nature of electrode
particles, there are a few other important differences between the
implementation of electrostatic interactions in ConstantPotentialForce and that
in NonbondedForce using PME:
1. ConstantPotentialForce does not include any capability for computing
Lennard-Jones interactions. If Lennard-Jones interactions as NonbondedForce
computes them are desired, a separate NonbondedForce should be added to the
system with the appropriate sigma and epsilon parameters set, and with all
charges set to zero.
2. For the solved charges on electrode particles to be valid, all particles in
the system must use a single ConstantPotentialForce. Setting charges in more
than one ConstantPotentialForce, or a NonbondedForce, will produce invalid
results, as the solution of electrode charges requires global minimization of
the total electrostatic energy of the system.
3. The Gaussian charge correction to pairwise interactions given by
:math:`E_{gauss}` above is calculated using the minimum image convention and
truncated with a fixed cutoff :math:`r_{cut}`. If decreasing :math:`r_{cut}`
to tune PME performance, ensure it stays large enough with respect to the
largest Gaussian width :math:`w_i` in the system. Using the formula for the
direct space error in section :numref:`coulomb-interaction-pme` with
:math:`1/w_i` in place of :math:`\alpha` (owing to the similarities in the
functional forms of these terms), :math:`r_{cut}\ge2.7w_{max}` should be
sufficient for the default error tolerance :math:`\delta=5\times10^{-4}`.
ConstantPotentialForce provides two algorithms to solve for electrode charges.
The matrix solver precomputes a capacitance matrix for the system before a
simulation and solves directly for charges, while the conjugate gradient (CG)
solver finds charges iteratively without requiring a matrix inversion. The
matrix solver may be faster if the number of electrode particles is small
enough, although it cannot be used unless the electrode particles are fixed in
place during a simulation by setting their masses to zero. Unless the positions
of electrode particles are allowed to fluctuate (in which case only the CG
solver can be used), both solvers can be benchmarked on a given problem to find
the one with the best performance.
GayBerneForce
*************
......
......@@ -192,7 +192,7 @@ Array2D<Real> Cholesky<Real>::solve(const Array2D<Real> &B)
return Array2D<Real>();
if (!isspd)
return Arran2D<Real>();
return Array2D<Real>();
Array2D<Real> X = B.copy();
......
......@@ -37,6 +37,7 @@
#include "openmm/BrownianIntegrator.h"
#include "openmm/CMAPTorsionForce.h"
#include "openmm/CMMotionRemover.h"
#include "openmm/ConstantPotentialForce.h"
#include "openmm/CustomAngleForce.h"
#include "openmm/CustomBondForce.h"
#include "openmm/CustomCentroidBondForce.h"
......@@ -655,6 +656,64 @@ public:
virtual void getLJPMEParameters(double& alpha, int& nx, int& ny, int& nz) const = 0;
};
/**
* This kernel is invoked by ConstantPotentialForce to calculate the forces acting on the system and the energy of the system.
*/
class CalcConstantPotentialForceKernel : public KernelImpl {
public:
static std::string Name() {
return "CalcConstantPotentialForce";
}
CalcConstantPotentialForceKernel(std::string name, const Platform& platform) : KernelImpl(name, platform) {
}
/**
* Initialize the kernel.
*
* @param system the System this kernel will be applied to
* @param force the ConstantPotentialForce this kernel will be used for
*/
virtual void initialize(const System& system, const ConstantPotentialForce& force) = 0;
/**
* Execute the kernel to calculate the forces and/or energy.
*
* @param context the context in which to execute this kernel
* @param includeForces true if forces should be calculated
* @param includeEnergy true if the energy should be calculated
* @return the potential energy due to the force
*/
virtual double execute(ContextImpl& context, bool includeForces, bool includeEnergy) = 0;
/**
* Copy changed parameters over to a context.
*
* @param context the context to copy parameters to
* @param force the ConstantPotentialForce to copy the parameters from
* @param firstParticle the index of the first particle whose parameters might have changed
* @param lastParticle the index of the last particle whose parameters might have changed
* @param firstException the index of the first exception whose parameters might have changed
* @param lastException the index of the last exception whose parameters might have changed
* @param firstElectrode the index of the first electrode whose parameters might have changed
* @param lastElectrode the index of the last electrode whose parameters might have changed
*/
virtual void copyParametersToContext(ContextImpl& context, const ConstantPotentialForce& force, int firstParticle, int lastParticle, int firstException, int lastException, int firstElectrode, int lastElectrode) = 0;
/**
* Get the parameters being used for PME.
*
* @param alpha the separation parameter
* @param nx the number of grid points along the X axis
* @param ny the number of grid points along the Y axis
* @param nz the number of grid points along the Z axis
*/
virtual void getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const = 0;
/**
* Get the charges on all particles.
*
* @param context the context to copy parameters to
* @param[out] charges a vector to populate with particle charges
*/
virtual void getCharges(ContextImpl& context, std::vector<double>& charges) = 0;
};
/**
* This kernel is invoked by CustomNonbondedForce to calculate the forces acting on the system and the energy of the system.
*/
......@@ -1698,18 +1757,21 @@ public:
* @param gridy the y size of the PME grid
* @param gridz the z size of the PME grid
* @param numParticles the number of particles in the system
* @param indices indices of particles to compute charge derivatives for
* @param alpha the Ewald blending parameter
* @param deterministic whether it should attempt to make the resulting forces deterministic
*/
virtual void initialize(int gridx, int gridy, int gridz, int numParticles, double alpha, bool deterministic) = 0;
virtual void initialize(int gridx, int gridy, int gridz, int numParticles, const std::vector<int>& indices, double alpha, bool deterministic) = 0;
/**
* Begin computing the force and energy.
*
* @param io an object that coordinates data transfer
* @param periodicBoxVectors the vectors defining the periodic box (measured in nm)
* @param includeEnergy true if potential energy should be computed
* @param io an object that coordinates data transfer
* @param periodicBoxVectors the vectors defining the periodic box (measured in nm)
* @param includeEnergy true if potential energy should be computed
* @param includeForces true if forces should be computed
* @param includeChargeDerivatives true if charge derivatives should be computed
*/
virtual void beginComputation(IO& io, const Vec3* periodicBoxVectors, bool includeEnergy) = 0;
virtual void beginComputation(IO& io, const Vec3* periodicBoxVectors, bool includeEnergy, bool includeForces, bool includeChargeDerivatives) = 0;
/**
* Finish computing the force and energy.
*
......@@ -1747,9 +1809,15 @@ public:
* should be ignored.
*/
virtual void setForce(float* force) = 0;
/**
* Record the charge derivatives calculated by the kernel.
*
* @param chargeDerivatives an array containing one element for each atom
* to compute charge derivatives for.
*/
virtual void setChargeDerivatives(float* chargeDerivatives) = 0;
};
/**
* This kernel performs the dispersion reciprocal space calculation for LJPME. In most cases, this
* calculation is done directly by CalcNonbondedForceKernel so this kernel is unneeded.
......
......@@ -37,6 +37,7 @@
#include "openmm/CMAPTorsionForce.h"
#include "openmm/CMMotionRemover.h"
#include "openmm/CompoundIntegrator.h"
#include "openmm/ConstantPotentialForce.h"
#include "openmm/CustomBondForce.h"
#include "openmm/CustomCentroidBondForce.h"
#include "openmm/CustomCompoundBondForce.h"
......
This diff is collapsed.
#ifndef OPENMM_CONSTANTPOTENTIALFORCEIMPL_H_
#define OPENMM_CONSTANTPOTENTIALFORCEIMPL_H_
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2025 Stanford University and the Authors. *
* Authors: Peter Eastman, Evan Pretti *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "ForceImpl.h"
#include "openmm/ConstantPotentialForce.h"
#include "openmm/Kernel.h"
#include <utility>
#include <set>
#include <string>
namespace OpenMM {
class System;
/**
* This is the internal implementation of ConstantPotentialForce.
*/
class OPENMM_EXPORT ConstantPotentialForceImpl : public ForceImpl {
public:
ConstantPotentialForceImpl(const ConstantPotentialForce& owner);
~ConstantPotentialForceImpl();
void initialize(ContextImpl& context);
const ConstantPotentialForce& getOwner() const {
return owner;
}
void updateContextState(ContextImpl& context, bool& forcesInvalid) {
// This force field doesn't update the state directly.
}
double calcForcesAndEnergy(ContextImpl& context, bool includeForces, bool includeEnergy, int groups);
std::map<std::string, double> getDefaultParameters() {
return std::map<std::string, double>(); // This force field doesn't define any parameters.
}
std::vector<std::string> getKernelNames();
void updateParametersInContext(ContextImpl& context, int firstParticle, int lastParticle, int firstException, int lastException, int firstElectrode, int lastElectrode);
void getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const;
void getCharges(ContextImpl& context, std::vector<double>& charges);
/**
* This is a utility routine that calculates the values to use for alpha and
* grid size when using Particle Mesh Ewald.
*/
static void calcPMEParameters(const System& system, const ConstantPotentialForce& force, double& alpha, int& xsize, int& ysize, int& zsize);
private:
const ConstantPotentialForce& owner;
Kernel kernel;
};
} // namespace OpenMM
#endif /*OPENMM_CONSTANTPOTENTIALFORCEIMPL_H_*/
......@@ -130,12 +130,49 @@ static int getNumProcessors() {
* Get whether this is an x86 CPU that supports AVX.
*/
static bool isAvxSupported() {
#ifdef __AVX__
int cpuInfo[4];
cpuid(cpuInfo, 0);
if (cpuInfo[0] >= 1) {
cpuid(cpuInfo, 1);
return ((cpuInfo[2] & ((int) 1 << 28)) != 0);
}
#endif /* __AVX__ */
return false;
}
/**
* Get whether this is an x86 CPU that supports AVX2.
*/
static bool isAvx2Supported() {
#ifdef __AVX2__
// Provide an alternative implementation of CPUID to support AVX2. On older
// non-Windows OSes the hardware.h support for CPUID doesn't set the CX register
// properly and gives the wrong answer when detecting AVX2 and beyond. On Windows
// the cpuid seems to work as expected so can be used.
#if !(defined(_WIN32) || defined(WIN32))
auto cpuid = [](int output[4], int functionnumber) {
int a, b, c, d;
__asm("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(functionnumber), "c"(0) : );
output[0] = a;
output[1] = b;
output[2] = c;
output[3] = d;
};
#endif
int cpuInfo[4];
cpuid(cpuInfo, 0);
if (cpuInfo[0] >= 7) {
cpuInfo[2] = 0;
cpuid(cpuInfo, 7);
return ((cpuInfo[1] & ((int) 1 << 5)) != 0);
}
#endif /* __AVX2__ */
return false;
}
......
......@@ -163,6 +163,14 @@ public:
ivec8 operator|(ivec8 other) const {
return _mm256_castps_si256(_mm256_or_ps(_mm256_castsi256_ps(val), _mm256_castsi256_ps(other.val)));
}
ivec8 operator==(ivec8 other) const {
// _mm256_cmpeq_epi32() compiles to an AVX2-only instruction, so compare
// the lower and upper 128-bit 4-vectors of ints separately.
return _mm256_setr_m128i(lowerVec() == other.lowerVec(), upperVec() == other.upperVec());
}
ivec8 operator!=(ivec8 other) const {
return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(*this == other), _mm256_castsi256_ps(_mm256_set1_epi32(0xFFFFFFFF))));
}
operator fvec8() const;
};
......
......@@ -37,35 +37,6 @@
// This file defines classes and functions to simplify vectorizing code with AVX.
bool isAvx2Supported() {
// Provide an alternative implementation of CPUID to support AVX2. On older
// non-Windows OSes the hardware.h support for CPUID doesn't set the CX register
// properly and gives the wrong answer when detecting AVX2 and beyond. On Windows
// the cpuid seems to work as expected so can be used.
#if !(defined(_WIN32) || defined(WIN32))
auto cpuid = [](int output[4], int functionnumber) {
int a, b, c, d;
__asm("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(functionnumber), "c"(0) : );
output[0] = a;
output[1] = b;
output[2] = c;
output[3] = d;
};
#endif
int cpuInfo[4];
cpuid(cpuInfo, 0);
if (cpuInfo[0] >= 7) {
cpuInfo[2] = 0;
cpuid(cpuInfo, 7);
return ((cpuInfo[1] & ((int) 1 << 5)) != 0);
}
return false;
}
/**
* Derive from fvec8 so that default implementations of everything are provided,
* but can be overriden with AVX2-specific variants where possible.
......
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2025 Stanford University and the Authors. *
* Authors: Peter Eastman, Evan Pretti *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "openmm/Force.h"
#include "openmm/OpenMMException.h"
#include "openmm/ConstantPotentialForce.h"
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/internal/ConstantPotentialForceImpl.h"
#include <cmath>
#include <map>
#include <sstream>
#include <utility>
using namespace OpenMM;
using namespace std;
ConstantPotentialForce::ConstantPotentialForce() : constantPotentialMethod(CG),
cutoffDistance(1.0), ewaldErrorTol(5e-4), alpha(0.0), cgErrorTol(1e-4),
chargeTarget(0.0), exceptionsUsePeriodic(false),
useChargeConstraint(false), usePreconditioner(true),
nx(0), ny(0), nz(0), numContexts(0) {
}
double ConstantPotentialForce::getCutoffDistance() const {
return cutoffDistance;
}
void ConstantPotentialForce::setCutoffDistance(double distance) {
cutoffDistance = distance;
}
double ConstantPotentialForce::getEwaldErrorTolerance() const {
return ewaldErrorTol;
}
void ConstantPotentialForce::setEwaldErrorTolerance(double tol) {
ewaldErrorTol = tol;
}
void ConstantPotentialForce::getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const {
alpha = this->alpha;
nx = this->nx;
ny = this->ny;
nz = this->nz;
}
void ConstantPotentialForce::setPMEParameters(double alpha, int nx, int ny, int nz) {
this->alpha = alpha;
this->nx = nx;
this->ny = ny;
this->nz = nz;
}
void ConstantPotentialForce::getPMEParametersInContext(const Context& context, double& alpha, int& nx, int& ny, int& nz) const {
dynamic_cast<const ConstantPotentialForceImpl&>(getImplInContext(context)).getPMEParameters(alpha, nx, ny, nz);
}
int ConstantPotentialForce::addParticle(double charge) {
particles.push_back(ParticleInfo(charge));
return particles.size()-1;
}
void ConstantPotentialForce::getParticleParameters(int index, double& charge) const {
ASSERT_VALID_INDEX(index, particles);
charge = particles[index].charge;
}
void ConstantPotentialForce::setParticleParameters(int index, double charge) {
ASSERT_VALID_INDEX(index, particles);
particles[index].charge = charge;
if (numContexts > 0) {
firstChangedParticle = min(index, firstChangedParticle);
lastChangedParticle = max(index, lastChangedParticle);
}
}
int ConstantPotentialForce::addException(int particle1, int particle2, double chargeProd, bool replace) {
map<pair<int, int>, int>::iterator iter = exceptionMap.find(pair<int, int>(particle1, particle2));
int newIndex;
if (iter == exceptionMap.end())
iter = exceptionMap.find(pair<int, int>(particle2, particle1));
if (iter != exceptionMap.end()) {
if (!replace) {
stringstream msg;
msg << "ConstantPotentialForce: There is already an exception for particles ";
msg << particle1;
msg << " and ";
msg << particle2;
throw OpenMMException(msg.str());
}
exceptions[iter->second] = ExceptionInfo(particle1, particle2, chargeProd);
newIndex = iter->second;
exceptionMap.erase(iter->first);
}
else {
exceptions.push_back(ExceptionInfo(particle1, particle2, chargeProd));
newIndex = exceptions.size()-1;
}
exceptionMap[pair<int, int>(particle1, particle2)] = newIndex;
return newIndex;
}
void ConstantPotentialForce::getExceptionParameters(int index, int& particle1, int& particle2, double& chargeProd) const {
ASSERT_VALID_INDEX(index, exceptions);
particle1 = exceptions[index].particle1;
particle2 = exceptions[index].particle2;
chargeProd = exceptions[index].chargeProd;
}
void ConstantPotentialForce::setExceptionParameters(int index, int particle1, int particle2, double chargeProd) {
ASSERT_VALID_INDEX(index, exceptions);
exceptions[index].particle1 = particle1;
exceptions[index].particle2 = particle2;
exceptions[index].chargeProd = chargeProd;
if (numContexts > 0) {
firstChangedException = min(index, firstChangedException);
lastChangedException = max(index, lastChangedException);
}}
ForceImpl* ConstantPotentialForce::createImpl() const {
if (numContexts == 0) {
// Begin tracking changes to particles and exceptions.
firstChangedParticle = particles.size();
lastChangedParticle = -1;
firstChangedException = exceptions.size();
lastChangedException = -1;
firstChangedElectrode = electrodes.size();
lastChangedElectrode = -1;
}
numContexts++;
return new ConstantPotentialForceImpl(*this);
}
void ConstantPotentialForce::createExceptionsFromBonds(const vector<pair<int, int> >& bonds, double coulomb14Scale) {
for (auto& bond : bonds)
if (bond.first < 0 || bond.second < 0 || bond.first >= particles.size() || bond.second >= particles.size())
throw OpenMMException("createExceptionsFromBonds: Illegal particle index in list of bonds");
// Find particles separated by 1, 2, or 3 bonds.
vector<set<int> > exclusions(particles.size());
vector<set<int> > bonded12(exclusions.size());
for (auto& bond : bonds) {
bonded12[bond.first].insert(bond.second);
bonded12[bond.second].insert(bond.first);
}
for (int i = 0; i < (int) exclusions.size(); ++i)
addExclusionsToSet(bonded12, exclusions[i], i, i, 2);
// Find particles separated by 1 or 2 bonds and create the exceptions.
for (int i = 0; i < (int) exclusions.size(); ++i) {
set<int> bonded13;
addExclusionsToSet(bonded12, bonded13, i, i, 1);
for (int j : exclusions[i]) {
if (j < i) {
if (bonded13.find(j) == bonded13.end()) {
// This is a 1-4 interaction.
const ParticleInfo& particle1 = particles[j];
const ParticleInfo& particle2 = particles[i];
const double chargeProd = coulomb14Scale*particle1.charge*particle2.charge;
addException(j, i, chargeProd);
}
else {
// This interaction should be completely excluded.
addException(j, i, 0.0);
}
}
}
}
}
void ConstantPotentialForce::addExclusionsToSet(const vector<set<int> >& bonded12, set<int>& exclusions, int baseParticle, int fromParticle, int currentLevel) const {
for (int i : bonded12[fromParticle]) {
if (i != baseParticle)
exclusions.insert(i);
if (currentLevel > 0)
addExclusionsToSet(bonded12, exclusions, baseParticle, i, currentLevel-1);
}
}
void ConstantPotentialForce::updateParametersInContext(Context& context) {
dynamic_cast<ConstantPotentialForceImpl&>(getImplInContext(context)).updateParametersInContext(getContextImpl(context),
firstChangedParticle, lastChangedParticle, firstChangedException, lastChangedException, firstChangedElectrode, lastChangedElectrode);
if (numContexts == 1) {
// We just updated the only existing context for this force, so we can
// reset the tracking of changed particles, exceptions, and electrodes.
firstChangedParticle = particles.size();
lastChangedParticle = -1;
firstChangedException = exceptions.size();
lastChangedException = -1;
firstChangedElectrode = electrodes.size();
lastChangedElectrode = -1;
}
}
bool ConstantPotentialForce::getExceptionsUsePeriodicBoundaryConditions() const {
return exceptionsUsePeriodic;
}
void ConstantPotentialForce::setExceptionsUsePeriodicBoundaryConditions(bool periodic) {
exceptionsUsePeriodic = periodic;
}
ConstantPotentialForce::ConstantPotentialMethod ConstantPotentialForce::getConstantPotentialMethod() const {
return constantPotentialMethod;
}
void ConstantPotentialForce::setConstantPotentialMethod(ConstantPotentialMethod method) {
if (method < 0 || method > 1) {
throw OpenMMException("ConstantPotentialForce: Illegal value for constant potential method");
}
constantPotentialMethod = method;
}
bool ConstantPotentialForce::getUsePreconditioner() const {
return usePreconditioner;
}
void ConstantPotentialForce::setUsePreconditioner(bool use) {
usePreconditioner = use;
}
double ConstantPotentialForce::getCGErrorTolerance() const {
return cgErrorTol;
}
void ConstantPotentialForce::setCGErrorTolerance(double tol) {
cgErrorTol = tol;
}
int ConstantPotentialForce::addElectrode(const std::set<int>& electrodeParticles, double potential, double gaussianWidth, double thomasFermiScale) {
electrodes.push_back(ElectrodeInfo(electrodeParticles, potential, gaussianWidth, thomasFermiScale));
return electrodes.size() - 1;
}
void ConstantPotentialForce::getElectrodeParameters(int index, std::set<int>& electrodeParticles, double& potential, double& gaussianWidth, double& thomasFermiScale) const {
ASSERT_VALID_INDEX(index, electrodes);
electrodeParticles = electrodes[index].particles;
potential = electrodes[index].potential;
gaussianWidth = electrodes[index].gaussianWidth;
thomasFermiScale = electrodes[index].thomasFermiScale;
}
void ConstantPotentialForce::setElectrodeParameters(int index, const std::set<int>& electrodeParticles, double potential, double gaussianWidth, double thomasFermiScale) {
ASSERT_VALID_INDEX(index, electrodes);
electrodes[index].particles = electrodeParticles;
electrodes[index].potential = potential;
electrodes[index].gaussianWidth = gaussianWidth;
electrodes[index].thomasFermiScale = thomasFermiScale;
if (numContexts > 0) {
firstChangedElectrode = min(index, firstChangedElectrode);
lastChangedElectrode = max(index, lastChangedElectrode);
}
}
bool ConstantPotentialForce::getUseChargeConstraint() const {
return useChargeConstraint;
}
void ConstantPotentialForce::setUseChargeConstraint(bool use) {
useChargeConstraint = use;
}
double ConstantPotentialForce::getChargeConstraintTarget() const {
return chargeTarget;
}
void ConstantPotentialForce::setChargeConstraintTarget(double charge) {
chargeTarget = charge;
}
void ConstantPotentialForce::getExternalField(Vec3& field) const {
field = externalField;
}
void ConstantPotentialForce::setExternalField(const Vec3& field) {
externalField = field;
}
void ConstantPotentialForce::getCharges(Context& context, std::vector<double>& charges) const {
dynamic_cast<ConstantPotentialForceImpl&>(getImplInContext(context)).getCharges(getContextImpl(context), charges);
}
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2025 Stanford University and the Authors. *
* Authors: Peter Eastman, Evan Pretti *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#ifdef WIN32
#define _USE_MATH_DEFINES // Needed to get M_PI
#endif
#include "openmm/OpenMMException.h"
#include "openmm/internal/ContextImpl.h"
#include "openmm/internal/Messages.h"
#include "openmm/internal/ConstantPotentialForceImpl.h"
#include "openmm/kernels.h"
#include <cmath>
#include <map>
#include <sstream>
#include <algorithm>
using namespace OpenMM;
using namespace std;
ConstantPotentialForceImpl::ConstantPotentialForceImpl(const ConstantPotentialForce& owner) : owner(owner) {
forceGroup = owner.getForceGroup();
}
ConstantPotentialForceImpl::~ConstantPotentialForceImpl() {
}
void ConstantPotentialForceImpl::initialize(ContextImpl& context) {
kernel = context.getPlatform().createKernel(CalcConstantPotentialForceKernel::Name(), context);
const System& system = context.getSystem();
if (owner.getNumParticles() != system.getNumParticles())
throw OpenMMException("ConstantPotentialForce must have exactly as many particles as the System it belongs to.");
// Check for errors in the specification of exceptions.
vector<set<int> > exceptions(owner.getNumParticles());
for (int i = 0; i < owner.getNumExceptions(); i++) {
int particle[2];
double chargeProd;
owner.getExceptionParameters(i, particle[0], particle[1], chargeProd);
int minp = min(particle[0], particle[1]);
int maxp = max(particle[0], particle[1]);
for (int j = 0; j < 2; j++) {
if (particle[j] < 0 || particle[j] >= owner.getNumParticles()) {
stringstream msg;
msg << "ConstantPotentialForce: Illegal particle index for an exception: ";
msg << particle[j];
throw OpenMMException(msg.str());
}
}
if (exceptions[minp].count(maxp) > 0) {
stringstream msg;
msg << "ConstantPotentialForce: Multiple exceptions are specified for particles ";
msg << particle[0];
msg << " and ";
msg << particle[1];
throw OpenMMException(msg.str());
}
exceptions[minp].insert(maxp);
}
// Check for problems with the periodic box vectors.
Vec3 boxVectors[3];
system.getDefaultPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]);
double cutoff = owner.getCutoffDistance();
if (cutoff > 0.5*boxVectors[0][0] || cutoff > 0.5*boxVectors[1][1] || cutoff > 0.5*boxVectors[2][2])
throw OpenMMException("ConstantPotentialForce: "+Messages::cutoffTooLarge);
// Check for errors in the specification of electrodes.
vector<int> electrodeIndexMap(owner.getNumParticles(), -1);
vector<int> electrodeIndices;
for (int electrode = 0; electrode < owner.getNumElectrodes(); electrode++) {
std::set<int> particles;
double potential;
double gaussianWidth;
double thomasFermiScale;
owner.getElectrodeParameters(electrode, particles, potential, gaussianWidth, thomasFermiScale);
for (int particle : particles) {
if (electrodeIndexMap[particle] != -1) {
stringstream msg;
msg << "ConstantPotentialForce: Particle " << particle << " belongs to electrodes " << electrodeIndexMap[particle] << " and " << electrode;
throw OpenMMException(msg.str());
}
electrodeIndexMap[particle] = electrode;
electrodeIndices.push_back(particle);
}
if (gaussianWidth < 0) {
stringstream msg;
msg << "ConstantPotentialForce: Electrode " << electrode << " has negative Gaussian width";
throw OpenMMException(msg.str());
}
if (thomasFermiScale < 0) {
stringstream msg;
msg << "ConstantPotentialForce: Electrode " << electrode << " has negative Thomas-Fermi scale";
throw OpenMMException(msg.str());
}
}
// Make sure no exceptions involve electrode particles.
for (int i = 0; i < owner.getNumExceptions(); i++) {
int particle1, particle2;
double chargeProd;
owner.getExceptionParameters(i, particle1, particle2, chargeProd);
int electrode1 = electrodeIndexMap[particle1];
int electrode2 = electrodeIndexMap[particle2];
if (electrode1 != -1) {
stringstream msg;
msg << "ConstantPotentialForce: Particle " << particle1 << " belongs to exception " << i << " and electrode " << electrode1;
throw OpenMMException(msg.str());
}
if (electrode2 != -1) {
stringstream msg;
msg << "ConstantPotentialForce: Particle " << particle2 << " belongs to exception " << i << " and electrode " << electrode2;
throw OpenMMException(msg.str());
}
}
// Make sure that we do not try to apply a constraint when zero electrode
// particles are present.
if (!electrodeIndices.size() && owner.getUseChargeConstraint()) {
throw OpenMMException("At least one electrode particle must exist to apply a total charge constraint");
}
// If we are precomputing a matrix, electrode particles must be frozen.
if (owner.getConstantPotentialMethod() == ConstantPotentialForce::ConstantPotentialMethod::Matrix) {
for (int i = 0; i < electrodeIndices.size(); i++) {
if (system.getParticleMass(electrodeIndices[i]) != 0.0) {
throw OpenMMException("Electrode particles must have zero mass for the matrix method");
}
}
}
kernel.getAs<CalcConstantPotentialForceKernel>().initialize(context.getSystem(), owner);
}
double ConstantPotentialForceImpl::calcForcesAndEnergy(ContextImpl& context, bool includeForces, bool includeEnergy, int groups) {
if ((groups & (1 << forceGroup)) != 0) {
return kernel.getAs<CalcConstantPotentialForceKernel>().execute(context, includeForces, includeEnergy);
}
return 0.0;
}
std::vector<std::string> ConstantPotentialForceImpl::getKernelNames() {
std::vector<std::string> names;
names.push_back(CalcConstantPotentialForceKernel::Name());
return names;
}
void ConstantPotentialForceImpl::updateParametersInContext(ContextImpl& context, int firstParticle, int lastParticle, int firstException, int lastException, int firstElectrode, int lastElectrode) {
kernel.getAs<CalcConstantPotentialForceKernel>().copyParametersToContext(context, owner, firstParticle, lastParticle, firstException, lastException, firstElectrode, lastElectrode);
context.systemChanged();
}
void ConstantPotentialForceImpl::getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const {
kernel.getAs<CalcConstantPotentialForceKernel>().getPMEParameters(alpha, nx, ny, nz);
}
void ConstantPotentialForceImpl::getCharges(ContextImpl& context, std::vector<double>& charges) {
kernel.getAs<CalcConstantPotentialForceKernel>().getCharges(context, charges);
}
void ConstantPotentialForceImpl::calcPMEParameters(const System& system, const ConstantPotentialForce& force, double& alpha, int& xsize, int& ysize, int& zsize) {
force.getPMEParameters(alpha, xsize, ysize, zsize);
if (alpha == 0.0) {
Vec3 boxVectors[3];
system.getDefaultPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]);
double tol = force.getEwaldErrorTolerance();
alpha = (1.0/force.getCutoffDistance())*std::sqrt(-log(2.0*tol));
xsize = max((int) ceil(2*alpha*boxVectors[0][0]/(3*pow(tol, 0.2))), 6);
ysize = max((int) ceil(2*alpha*boxVectors[1][1]/(3*pow(tol, 0.2))), 6);
zsize = max((int) ceil(2*alpha*boxVectors[2][2]/(3*pow(tol, 0.2))), 6);
}
}
......@@ -122,9 +122,37 @@ public:
}
/**
* Copy the values in the array to a vector.
*
* @param data the vector to receive the values
* @param convert if true, automatic conversions between single and double
* precision will be performed as necessary
*/
template <class T>
void download(std::vector<T>& data) const {
void download(std::vector<T>& data, bool convert=false) const {
if (convert && sizeof(T) != getElementSize()) {
if (sizeof(T) == 2*getElementSize()) {
// Convert values from single to double precision.
std::vector<float> v(getElementSize()*getSize()/sizeof(float));
download(&v[0], true);
if (data.size() != getSize())
data.resize(getSize());
double* d = reinterpret_cast<double*>(&data[0]);
for (int i = 0; i < v.size(); i++)
d[i] = (double) v[i];
return;
}
if (2*sizeof(T) == getElementSize()) {
// Convert values from double to single precision.
std::vector<double> v(getElementSize()*getSize()/sizeof(double));
download(&v[0], true);
if (data.size() != getSize())
data.resize(getSize());
float* d = reinterpret_cast<float*>(&data[0]);
for (int i = 0; i < v.size(); i++)
d[i] = (float) v[i];
return;
}
}
if (sizeof(T) != getElementSize())
throw OpenMMException("Error downloading array "+getName()+": The specified vector has the wrong element size");
if (data.size() != getSize())
......
#ifndef OPENMM_COMMONCALCCONSTANTPOTENTIALFORCE_H_
#define OPENMM_COMMONCALCCONSTANTPOTENTIALFORCE_H_
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2025 Stanford University and the Authors. *
* Authors: Evan Pretti *
* Contributors: *
* *
* This program 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 3 of the License, or *
* (at your option) any later version. *
* *
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* -------------------------------------------------------------------------- */
#include "openmm/kernels.h"
#include "openmm/common/ComputeArray.h"
#include "openmm/common/ComputeContext.h"
#include "openmm/common/ComputeEvent.h"
#include "openmm/common/ComputeQueue.h"
#include "openmm/common/ComputeSort.h"
#include "openmm/common/FFT3D.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace OpenMM {
class CommonCalcConstantPotentialForceKernel;
/**
* A generic charge solver for the constant potential method.
*/
class CommonConstantPotentialSolver {
public:
/**
* Creates a CommonConstantPotentialSolver.
*
* @param cc the compute context (should be selected)
* @param numParticles the number of particles
* @param numElectrodeParticles the number of electrode (fluctuating-charge) particles (should be positive)
* @param paddedProblemSize the padded number of electrode particles
*/
CommonConstantPotentialSolver(ComputeContext& cc, int numParticles, int numElectrodeParticles, int paddedProblemSize);
virtual ~CommonConstantPotentialSolver();
/**
* Sets up solver kernels. Should be called by derived classes if
* overridden.
*
* @param kernel main constant potential kernel
*/
virtual void compileKernels(CommonCalcConstantPotentialForceKernel& kernel);
/**
* Mark precomputed data stored by the solver as invalid due to a change in
* electrode parameters.
*/
void invalidate();
/**
* Mark any existing solution stored by the solver as invalid due to a
* change in system parameters.
*/
void discardSavedSolution();
/**
* Solves for charges if needed.
*
* @param kernel main constant potential kernel
*/
void solve(CommonCalcConstantPotentialForceKernel& kernel);
/**
* Solves for charges.
*
* @param kernel main constant potential kernel
*/
virtual void solveImpl(CommonCalcConstantPotentialForceKernel& kernel) = 0;
/**
* Retrieves a list of arrays of initial guess charges to be reordered.
*
* @param arrays Arrays to be reordered.
*/
virtual void getGuessChargeArrays(std::vector<ComputeArray*>& arrays);
protected:
int numParticles, numElectrodeParticles, paddedProblemSize;
bool valid, hasSavedSolution;
Vec3 savedBoxVectors[3];
ComputeArray savedPositions;
ComputeArray savedCharges;
ComputeArray checkSavedPositionsKernelResult;
ComputeKernel checkSavedPositionsKernel;
};
/**
* A constant potential solver using direct inversion of the Coulomb matrix.
* Suitable only when electrode particle positions are fixed.
*/
class CommonConstantPotentialMatrixSolver : public CommonConstantPotentialSolver {
private:
Vec3 boxVectors[3];
ComputeArray electrodePosData;
ComputeArray capacitance;
ComputeArray constraintVector;
ComputeArray checkSavedElectrodePositionsKernelResult;
ComputeKernel checkSavedElectrodePositionsKernel;
ComputeKernel saveElectrodePositionsKernel;
ComputeKernel solveKernel;
public:
/**
* Creates a CommonConstantPotentialMatrixSolver.
*
* @param cc the compute context (should be selected)
* @param numParticles the number of particles
* @param numElectrodeParticles the number of electrode (fluctuating-charge) particles
* @param paddedProblemSize the padded number of electrode particles
*/
CommonConstantPotentialMatrixSolver(ComputeContext& cc, int numParticles, int numElectrodeParticles, int paddedProblemSize);
/**
* Sets up solver kernels.
*
* @param kernel main constant potential kernel
*/
void compileKernels(CommonCalcConstantPotentialForceKernel& kernel);
/**
* Solves for charges.
*
* @param kernel main constant potential kernel
*/
void solveImpl(CommonCalcConstantPotentialForceKernel& kernel);
private:
/**
* Solves for charges.
*
* @param kernel main constant potential kernel
*/
void ensureValid(CommonCalcConstantPotentialForceKernel& kernel);
};
/**
* A constant potential solver using direct inversion of the Coulomb matrix.
* Suitable only when electrode particle positions are fixed.
*/
class CommonConstantPotentialCGSolver : public CommonConstantPotentialSolver {
private:
Vec3 boxVectors[3];
bool precondRequested, precondActivated;
int threadBlockCount, threadBlockSize;
ComputeArray precondVector;
ComputeArray q;
ComputeArray grad;
ComputeArray projGrad;
ComputeArray precGrad;
ComputeArray qStep;
ComputeArray gradStep;
ComputeArray grad0;
ComputeArray qLast;
ComputeArray blockSums1;
ComputeArray blockSums2;
ComputeArray convergedResult;
ComputeKernel solveInitializeStep1Kernel;
ComputeKernel solveInitializeStep2Kernel;
ComputeKernel solveInitializeStep3Kernel;
ComputeKernel solveLoopStep1Kernel;
ComputeKernel solveLoopStep2Kernel;
ComputeKernel solveLoopStep3Kernel;
ComputeKernel solveLoopStep4Kernel;
ComputeKernel solveLoopStep5Kernel;
ComputeEvent convergedDownloadStartEvent;
ComputeEvent convergedDownloadFinishEvent;
ComputeQueue convergedDownloadQueue;
public:
/**
* Creates a CommonConstantPotentialCGSolver.
*
* @param cc the compute context (should be selected)
* @param numParticles the number of particles
* @param numElectrodeParticles the number of electrode (fluctuating-charge) particles
* @param paddedProblemSize the padded number of electrode particles
* @param precond whether or not to use a preconditioner
*/
CommonConstantPotentialCGSolver(ComputeContext& cc, int numParticles, int numElectrodeParticles, int paddedProblemSize, bool precond);
/**
* Sets up solver kernels.
*
* @param kernel main constant potential kernel
*/
virtual void compileKernels(CommonCalcConstantPotentialForceKernel& kernel);
/**
* Solves for charges.
*
* @param kernel main constant potential kernel
*/
void solveImpl(CommonCalcConstantPotentialForceKernel& kernel);
/**
* Retrieves a list of arrays of initial guess charges to be reordered.
*
* @param arrays Arrays to be reordered.
*/
virtual void getGuessChargeArrays(std::vector<ComputeArray*>& arrays);
private:
/**
* Solves for charges.
*
* @param kernel main constant potential kernel
*/
void ensureValid(CommonCalcConstantPotentialForceKernel& kernel);
};
/**
* This kernel is invoked by ConstantPotentialForce to calculate the forces acting on the system.
*/
class CommonCalcConstantPotentialForceKernel : public CalcConstantPotentialForceKernel {
friend class CommonConstantPotentialSolver;
friend class CommonConstantPotentialMatrixSolver;
friend class CommonConstantPotentialCGSolver;
public:
CommonCalcConstantPotentialForceKernel(std::string name, const Platform& platform, ComputeContext& cc, const System& system) : CalcConstantPotentialForceKernel(name, platform),
cc(cc), info(NULL), solver(NULL), hasInitializedKernel(false) {
}
~CommonCalcConstantPotentialForceKernel();
/**
* Initialize the kernel. Subclasses should call this from their initialize() method.
*
* @param system the System this kernel will be applied to
* @param force the ConstantPotentialForce this kernel will be used for
* @param deviceIsCpu whether the device this calculation is running on is a CPU
* @param useFixedPointChargeSpreading whether PME charge spreading should be done in fixed point or floating point
*/
void commonInitialize(const System& system, const ConstantPotentialForce& force, bool deviceIsCpu, bool useFixedPointChargeSpreading);
/**
* Execute the kernel to calculate the forces and/or energy.
*
* @param context the context in which to execute this kernel
* @param includeForces true if forces should be calculated
* @param includeEnergy true if the energy should be calculated
* @return the potential energy due to the force
*/
double execute(ContextImpl& context, bool includeForces, bool includeEnergy);
/**
* Copy changed parameters over to a context.
*
* @param context the context to copy parameters to
* @param force the ConstantPotentialForce to copy the parameters from
* @param firstParticle the index of the first particle whose parameters might have changed
* @param lastParticle the index of the last particle whose parameters might have changed
* @param firstException the index of the first exception whose parameters might have changed
* @param lastException the index of the last exception whose parameters might have changed
* @param firstElectrode the index of the first electrode whose parameters might have changed
* @param lastElectrode the index of the last electrode whose parameters might have changed
*/
void copyParametersToContext(ContextImpl& context, const ConstantPotentialForce& force, int firstParticle, int lastParticle, int firstException, int lastException, int firstElectrode, int lastElectrode);
/**
* Get the parameters being used for PME.
*
* @param alpha the separation parameter
* @param nx the number of grid points along the X axis
* @param ny the number of grid points along the Y axis
* @param nz the number of grid points along the Z axis
*/
void getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const;
/**
* Get the charges on all particles.
*
* @param context the context to copy parameters to
* @param[out] charges a vector to populate with particle charges
*/
void getCharges(ContextImpl& context, std::vector<double>& charges);
private:
void ensureInitialized(ContextImpl& context);
double doEnergyForces(bool includeForces, bool includeEnergy);
void initDoDerivatives();
void doDerivatives(bool init = true);
void pmeSetup();
void pmeCompileKernels();
void initPmeExecute();
void pmeExecute(bool includeEnergy, bool includeForces, bool includeChargeDerivatives, bool init = true);
void setKernelInputs(bool includeEnergy, bool includeForces);
class SortTrait : public ComputeSortImpl::SortTrait {
int getDataSize() const {return 8;}
int getKeySize() const {return 4;}
const char* getDataType() const {return "int2";}
const char* getKeyType() const {return "int";}
const char* getMinKey() const {return "(-2147483647-1)";}
const char* getMaxKey() const {return "2147483647";}
const char* getMaxValue() const {return "make_int2(2147483647, 2147483647)";}
const char* getSortKey() const {return "value.y";}
};
class ForceInfo;
class ReorderListener;
ComputeContext& cc;
ForceInfo* info;
CommonConstantPotentialSolver* solver;
bool hasInitializedKernel, hasElectrodes, mustUpdateNonElectrodeCharges, mustUpdateElectrodeCharges, pmeShouldSort;
int numParticles, numElectrodeParticles, numElectrodes, chunkSize, chunkCount, paddedProblemSize;
ComputeArray charges;
ComputeArray nonElectrodeCharges;
ComputeArray electrodeCharges;
ComputeArray chargeDerivatives;
ComputeArray chargeDerivativesFixed;
ComputeArray totalChargeBuffer;
ComputeArray sysToElec;
ComputeArray elecToSys;
ComputeArray sysElec;
ComputeArray elecElec;
ComputeArray electrodeParams;
ComputeArray exclusionScales;
ComputeArray exceptionScales;
ComputeArray pmeGrid1;
ComputeArray pmeGrid2;
ComputeArray pmeBsplineModuliX;
ComputeArray pmeBsplineModuliY;
ComputeArray pmeBsplineModuliZ;
ComputeArray pmeAtomGridIndex;
ComputeArray posCellOffsets;
ComputeSort sort;
FFT3D fft;
ComputeKernel updateNonElectrodeChargesKernel;
ComputeKernel updateElectrodeChargesKernel;
ComputeKernel getTotalChargeKernel;
ComputeKernel evaluateSelfEnergyForcesKernel;
ComputeKernel evaluateDirectDerivativesKernel;
ComputeKernel finishDerivativesKernel;
ComputeKernel pmeGridIndexKernel;
ComputeKernel pmeSpreadChargeKernel;
ComputeKernel pmeFinishSpreadChargeKernel;
ComputeKernel pmeConvolutionKernel;
ComputeKernel pmeEvalEnergyKernel;
ComputeKernel pmeInterpolateForceKernel;
ComputeKernel pmeInterpolateChargeDerivativesKernel;
std::vector<mm_int4> hostPosCellOffsets;
std::vector<double> setCharges, hostNonElectrodeCharges, hostElectrodeCharges;
std::vector<std::pair<int, int> > exclusions;
std::vector<int> exceptions;
std::vector<int> hostSysToElec, hostElecToSys, hostSysElec, hostElecElec;
std::vector<mm_double4> hostElectrodeParams;
std::map<int, int> exceptionIndex;
std::map<std::string, std::string> pmeDefines;
ConstantPotentialForce::ConstantPotentialMethod method;
Vec3 boxVectors[3], externalField;
double cutoff, ewaldAlpha, chargeTarget, cgErrorTol;
int maxThreadBlockSize, gridSizeX, gridSizeY, gridSizeZ;
bool deviceIsCpu, useFixedPointChargeSpreading, usePosqCharges, useChargeConstraint;
int forceGroup;
static const int PmeOrder = 5;
static const double SELF_ALPHA_SCALE, SELF_ETA_SCALE, SELF_TF_SCALE, PLASMA_SCALE;
};
} // namespace OpenMM
#endif /* OPENMM_COMMONCALCCONSTANTPOTENTIALFORCE_H_ */
......@@ -107,8 +107,8 @@ public:
* Copy the values in the Buffer to a vector.
*/
template <class T>
void download(std::vector<T>& data) const {
ArrayInterface::download(data);
void download(std::vector<T>& data, bool convert=false) const {
ArrayInterface::download(data, convert);
}
/**
* Copy the values from host memory to the array.
......
This diff is collapsed.
......@@ -130,6 +130,8 @@ public:
addForcesKernel->setArg(1, cc.getLongForceBuffer());
addForcesKernel->execute(cc.getNumAtoms());
}
void setChargeDerivatives(float* chargeDerivatives) {
}
private:
ComputeContext& cc;
vector<mm_float4> posq;
......@@ -144,7 +146,7 @@ public:
void computeForceAndEnergy(bool includeForces, bool includeEnergy, int groups) {
Vec3 boxVectors[3];
cc.getPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]);
pme.getAs<CalcPmeReciprocalForceKernel>().beginComputation(io, boxVectors, includeEnergy);
pme.getAs<CalcPmeReciprocalForceKernel>().beginComputation(io, boxVectors, includeEnergy, true, false);
}
private:
ComputeContext& cc;
......@@ -407,6 +409,7 @@ void CommonCalcNonbondedForceKernel::commonInitialize(const System& system, cons
pmeDefines["PME_ORDER"] = cc.intToString(PmeOrder);
pmeDefines["NUM_ATOMS"] = cc.intToString(numParticles);
pmeDefines["PADDED_NUM_ATOMS"] = cc.intToString(cc.getPaddedNumAtoms());
pmeDefines["NUM_INDICES"] = "0";
pmeDefines["RECIP_EXP_FACTOR"] = cc.doubleToString(M_PI*M_PI/(alpha*alpha));
pmeDefines["GRID_SIZE_X"] = cc.intToString(gridSizeX);
pmeDefines["GRID_SIZE_Y"] = cc.intToString(gridSizeY);
......@@ -422,7 +425,7 @@ void CommonCalcNonbondedForceKernel::commonInitialize(const System& system, cons
try {
cpuPme = getPlatform().createKernel(CalcPmeReciprocalForceKernel::Name(), *cc.getContextImpl());
cpuPme.getAs<CalcPmeReciprocalForceKernel>().initialize(gridSizeX, gridSizeY, gridSizeZ, numParticles, alpha, false);
cpuPme.getAs<CalcPmeReciprocalForceKernel>().initialize(gridSizeX, gridSizeY, gridSizeZ, numParticles, {}, alpha, false);
ComputeProgram program = cc.compileProgram(CommonKernelSources::pme, pmeDefines);
ComputeKernel addForcesKernel = program->createKernel("addForces");
pmeio = new PmeIO(cc, addForcesKernel);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment