Unverified Commit 74912095 authored by Peter Eastman's avatar Peter Eastman Committed by GitHub
Browse files

Refactor reference PME (#5131)

parent c24c619e
...@@ -255,28 +255,26 @@ void CpuNonbondedForce::calculateReciprocalIxn(int numberOfAtoms, float* posq, c ...@@ -255,28 +255,26 @@ void CpuNonbondedForce::calculateReciprocalIxn(int numberOfAtoms, float* posq, c
float recipCoeff = (float)(ONE_4PI_EPS0*4*PI_M/(periodicBoxVectors[0][0] * periodicBoxVectors[1][1] * periodicBoxVectors[2][2]) /epsilon); float recipCoeff = (float)(ONE_4PI_EPS0*4*PI_M/(periodicBoxVectors[0][0] * periodicBoxVectors[1][1] * periodicBoxVectors[2][2]) /epsilon);
if (pme) { if (pme) {
pme_t pmedata; ReferencePME pme(alphaEwald, numberOfAtoms, meshDim, 5, 1);
pme_init(&pmedata, alphaEwald, numberOfAtoms, meshDim, 5, 1);
vector<double> charges(numberOfAtoms); vector<double> charges(numberOfAtoms);
for (int i = 0; i < numberOfAtoms; i++) for (int i = 0; i < numberOfAtoms; i++)
charges[i] = posq[4*i+3]; charges[i] = posq[4*i+3];
double recipEnergy = 0.0; double recipEnergy = 0.0;
pme_exec(pmedata, atomCoordinates, forces, charges, periodicBoxVectors, &recipEnergy); pme.exec(atomCoordinates, forces, charges, periodicBoxVectors, recipEnergy);
if (totalEnergy) if (totalEnergy)
*totalEnergy += recipEnergy; *totalEnergy += recipEnergy;
pme_destroy(pmedata);
if (ljpme) { if (ljpme) {
// Dispersion reciprocal space terms // Dispersion reciprocal space terms
pme_init(&pmedata,alphaDispersionEwald,numberOfAtoms,dispersionMeshDim,5,1); ReferencePME ljpme(alphaDispersionEwald,numberOfAtoms,dispersionMeshDim,5,1);
std::vector<Vec3> dpmeforces; vector<Vec3> dpmeforces;
for (int i = 0; i < numberOfAtoms; i++){ for (int i = 0; i < numberOfAtoms; i++){
charges[i] = C6params[i]; charges[i] = C6params[i];
dpmeforces.push_back(Vec3()); dpmeforces.push_back(Vec3());
} }
double recipDispersionEnergy = 0.0; double recipDispersionEnergy = 0.0;
pme_exec_dpme(pmedata,atomCoordinates,dpmeforces,charges,periodicBoxVectors,&recipDispersionEnergy); ljpme.exec_dpme(atomCoordinates, dpmeforces, charges, periodicBoxVectors, recipDispersionEnergy);
for (int i = 0; i < numberOfAtoms; i++){ for (int i = 0; i < numberOfAtoms; i++){
forces[i][0] += dpmeforces[i][0]; forces[i][0] += dpmeforces[i][0];
forces[i][1] += dpmeforces[i][1]; forces[i][1] += dpmeforces[i][1];
...@@ -284,8 +282,6 @@ void CpuNonbondedForce::calculateReciprocalIxn(int numberOfAtoms, float* posq, c ...@@ -284,8 +282,6 @@ void CpuNonbondedForce::calculateReciprocalIxn(int numberOfAtoms, float* posq, c
} }
if (totalEnergy) if (totalEnergy)
*totalEnergy += recipDispersionEnergy; *totalEnergy += recipDispersionEnergy;
pme_destroy(pmedata);
} }
} }
......
...@@ -90,14 +90,14 @@ public: ...@@ -90,14 +90,14 @@ public:
* @param elecToSys mapping from electrode particle indices to system particle indices * @param elecToSys mapping from electrode particle indices to system particle indices
* @param electrodeParamArray electrode particle parameters * @param electrodeParamArray electrode particle parameters
* @param conp constant potential derivative evaluation class * @param conp constant potential derivative evaluation class
* @param pmeData reference PME solver * @param pme reference PME solver
*/ */
virtual void solve(int numParticles, int numElectrodeParticles, virtual void solve(int numParticles, int numElectrodeParticles,
const std::vector<Vec3>& posData, std::vector<double>& charges, const std::vector<Vec3>& posData, std::vector<double>& charges,
const std::vector<std::set<int>>& exclusions, const std::vector<std::set<int>>& exclusions,
const std::vector<int>& sysToElec, const std::vector<int>& elecToSys, const std::vector<int>& sysToElec, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotential& conp, pme_t pmeData) = 0; ReferenceConstantPotential& conp, ReferencePME& pme) = 0;
}; };
/** /**
...@@ -149,14 +149,14 @@ public: ...@@ -149,14 +149,14 @@ public:
* @param elecToSys mapping from electrode particle indices to system particle indices * @param elecToSys mapping from electrode particle indices to system particle indices
* @param electrodeParamArray electrode particle parameters * @param electrodeParamArray electrode particle parameters
* @param conp constant potential derivative evaluation class * @param conp constant potential derivative evaluation class
* @param pmeData reference PME solver * @param pme reference PME solver
*/ */
void solve(int numParticles, int numElectrodeParticles, void solve(int numParticles, int numElectrodeParticles,
const std::vector<Vec3>& posData, std::vector<double>& charges, const std::vector<Vec3>& posData, std::vector<double>& charges,
const std::vector<std::set<int>>& exclusions, const std::vector<std::set<int>>& exclusions,
const std::vector<int>& sysToElec, const std::vector<int>& elecToSys, const std::vector<int>& sysToElec, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotential& conp, pme_t pmeData); ReferenceConstantPotential& conp, ReferencePME& pme);
}; };
/** /**
...@@ -208,14 +208,14 @@ public: ...@@ -208,14 +208,14 @@ public:
* @param elecToSys mapping from electrode particle indices to system particle indices * @param elecToSys mapping from electrode particle indices to system particle indices
* @param electrodeParamArray electrode particle parameters * @param electrodeParamArray electrode particle parameters
* @param conp constant potential derivative evaluation class * @param conp constant potential derivative evaluation class
* @param pmeData reference PME solver * @param pme reference PME solver
*/ */
void solve(int numParticles, int numElectrodeParticles, void solve(int numParticles, int numElectrodeParticles,
const std::vector<Vec3>& posData, std::vector<double>& charges, const std::vector<Vec3>& posData, std::vector<double>& charges,
const std::vector<std::set<int>>& exclusions, const std::vector<std::set<int>>& exclusions,
const std::vector<int>& sysToElec, const std::vector<int>& elecToSys, const std::vector<int>& sysToElec, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotential& conp, pme_t pmeData); ReferenceConstantPotential& conp, ReferencePME& pme);
}; };
/** /**
...@@ -313,7 +313,7 @@ private: ...@@ -313,7 +313,7 @@ private:
* @param elecToSys mapping from electrode particle indices to system particle indices * @param elecToSys mapping from electrode particle indices to system particle indices
* @param electrodeParamArray electrode particle parameters * @param electrodeParamArray electrode particle parameters
* @param energy output system energy * @param energy output system energy
* @param pmeData reference PME solver * @param pme reference PME solver
*/ */
void getEnergyForces(int numParticles, int numElectrodeParticles, void getEnergyForces(int numParticles, int numElectrodeParticles,
const std::vector<Vec3>& posData, std::vector<Vec3>& forceData, const std::vector<Vec3>& posData, std::vector<Vec3>& forceData,
...@@ -321,7 +321,7 @@ private: ...@@ -321,7 +321,7 @@ private:
const std::vector<std::set<int>>& exclusions, const std::vector<std::set<int>>& exclusions,
const std::vector<int>& sysToElec, const std::vector<int>& elecToSys, const std::vector<int>& sysToElec, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
double* energy, pme_t pmeData); double* energy, ReferencePME& pme);
/** /**
* Computes energy derivatives with respect to charges. * Computes energy derivatives with respect to charges.
* *
...@@ -334,14 +334,14 @@ private: ...@@ -334,14 +334,14 @@ private:
* @param elecToSys mapping from electrode particle indices to system particle indices * @param elecToSys mapping from electrode particle indices to system particle indices
* @param electrodeParamArray electrode particle parameters * @param electrodeParamArray electrode particle parameters
* @param chargeDerivatives output charge derivatives * @param chargeDerivatives output charge derivatives
* @param pmeData reference PME solver * @param pme reference PME solver
*/ */
void getDerivatives(int numParticles, int numElectrodeParticles, void getDerivatives(int numParticles, int numElectrodeParticles,
const std::vector<Vec3>& posData, std::vector<double>& charges, const std::vector<Vec3>& posData, std::vector<double>& charges,
const std::vector<std::set<int>>& exclusions, const std::vector<std::set<int>>& exclusions,
const std::vector<int>& sysToElec, const std::vector<int>& elecToSys, const std::vector<int>& sysToElec, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
std::vector<double>& chargeDerivatives, pme_t pmeData); std::vector<double>& chargeDerivatives, ReferencePME& pme);
}; };
} // namespace OpenMM } // namespace OpenMM
......
/* /*
* Reference implementation of PME reciprocal space interactions. * Reference implementation of PME reciprocal space interactions.
* *
* Copyright (c) 2009, Erik Lindahl, Rossen Apostolov, Szilard Pall * Copyright (c) 2009-2025 Erik Lindahl, Rossen Apostolov, Szilard Pall, Peter Eastman
* All rights reserved. * All rights reserved.
* Contact: lindahl@cbr.su.se Stockholm University, Sweden. * Contact: lindahl@cbr.su.se Stockholm University, Sweden.
* *
...@@ -34,22 +34,19 @@ ...@@ -34,22 +34,19 @@
#include "openmm/Vec3.h" #include "openmm/Vec3.h"
#include "openmm/internal/windowsExport.h" #include "openmm/internal/windowsExport.h"
#include <array>
#include <complex>
#include <vector> #include <vector>
namespace OpenMM { namespace OpenMM {
typedef double rvec[3]; class OPENMM_EXPORT ReferencePME {
public:
/*
typedef struct pme *
pme_t;
/*
* Initialize a PME calculation and set up data structures * Initialize a PME calculation and set up data structures
* *
* Arguments: * Arguments:
* *
* ppme Pointer to an opaque pme_t object
* ewaldcoeff Coefficient derived from the beta factor to participate * ewaldcoeff Coefficient derived from the beta factor to participate
* direct/reciprocal space. See gromacs code for documentation! * direct/reciprocal space. See gromacs code for documentation!
* We assume that you are using nm units... * We assume that you are using nm units...
...@@ -58,77 +55,96 @@ pme_t; ...@@ -58,77 +55,96 @@ pme_t;
* pme_order Interpolation order, almost always 4 * pme_order Interpolation order, almost always 4
* epsilon_r Dielectric coefficient, typically 1.0. * epsilon_r Dielectric coefficient, typically 1.0.
*/ */
int OPENMM_EXPORT ReferencePME(double ewaldcoeff, int natoms, const int ngrid[3], int pme_order, double epsilon_r);
pme_init(pme_t* ppme,
double ewaldcoeff,
int natoms,
const int ngrid[3],
int pme_order,
double epsilon_r);
/* /*
* Evaluate reciprocal space PME energy and forces. * Evaluate reciprocal space PME energy and forces.
* *
* Args: * Args:
* *
* pme Opaque pme_t object, must have been initialized with pme_init()
* atomCoordinates Pointer to coordinate data array (nm) * atomCoordinates Pointer to coordinate data array (nm)
* forces Pointer to force data array (will be written as kJ/mol/nm) * forces Pointer to force data array (will be written as kJ/mol/nm)
* charges Array of charges (units of e) * charges Array of charges (units of e)
* periodicBoxVectors Simulation cell dimensions (nm) * periodicBoxVectors Simulation cell dimensions (nm)
* energy Total energy (will be written in units of kJ/mol) * energy Total energy (will be written in units of kJ/mol)
*/ */
int OPENMM_EXPORT void exec(const std::vector<OpenMM::Vec3>& atomCoordinates, std::vector<OpenMM::Vec3>& forces, const std::vector<double>& charges,
pme_exec(pme_t pme, const OpenMM::Vec3 periodicBoxVectors[3], double& energy);
const std::vector<OpenMM::Vec3>& atomCoordinates,
std::vector<OpenMM::Vec3>& forces,
const std::vector<double>& charges,
const OpenMM::Vec3 periodicBoxVectors[3],
double* energy);
/* /*
* Evaluate reciprocal space PME energy and charge derivatives. * Evaluate reciprocal space PME energy and charge derivatives.
* *
* Args: * Args:
* *
* pme Opaque pme_t object, must have been initialized with pme_init()
* atomCoordinates Pointer to coordinate data array (nm) * atomCoordinates Pointer to coordinate data array (nm)
* chargeDerivatives Pointer to charge derivative data array (will be written as kJ/mol/e) * chargeDerivatives Pointer to charge derivative data array (will be written as kJ/mol/e)
* chargeIndices Pointer to array of indices of particles to compute charge derivatives for * chargeIndices Pointer to array of indices of particles to compute charge derivatives for
* charges Array of charges (units of e) * charges Array of charges (units of e)
* periodicBoxVectors Simulation cell dimensions (nm) * periodicBoxVectors Simulation cell dimensions (nm)
*/ */
int OPENMM_EXPORT void exec_charge_derivatives(const std::vector<OpenMM::Vec3>& atomCoordinates, std::vector<double>& chargeDerivatives,
pme_exec_charge_derivatives(pme_t pme, const std::vector<int>& chargeIndices, const std::vector<double>& charges, const OpenMM::Vec3 periodicBoxVectors[3]);
const std::vector<OpenMM::Vec3>& atomCoordinates,
std::vector<double>& chargeDerivatives,
const std::vector<int>& chargeIndices,
const std::vector<double>& charges,
const OpenMM::Vec3 periodicBoxVectors[3]);
/** /**
* Evaluate reciprocal space PME dispersion energy and forces. * Evaluate reciprocal space PME dispersion energy and forces.
* *
* Args: * Args:
* *
* pme Opaque pme_t object, must have been initialized with pme_init()
* atomCoordinates Pointer to coordinate data array (nm) * atomCoordinates Pointer to coordinate data array (nm)
* forces Pointer to force data array (will be written as kJ/mol/nm) * forces Pointer to force data array (will be written as kJ/mol/nm)
* c6s Array of c6 coefficients (units of sqrt(kJ/mol).nm^3 ) * c6s Array of c6 coefficients (units of sqrt(kJ/mol).nm^3 )
* periodicBoxVectors Simulation cell dimensions (nm) * periodicBoxVectors Simulation cell dimensions (nm)
* energy Total energy (will be written in units of kJ/mol) * energy Total energy (will be written in units of kJ/mol)
*/ */
int OPENMM_EXPORT void exec_dpme(const std::vector<OpenMM::Vec3>& atomCoordinates, std::vector<OpenMM::Vec3>& forces, const std::vector<double>& c6s,
pme_exec_dpme(pme_t pme, const OpenMM::Vec3 periodicBoxVectors[3], double& energy);
const std::vector<OpenMM::Vec3>& atomCoordinates, private:
std::vector<OpenMM::Vec3>& forces, void calculate_bsplines_moduli();
const std::vector<double>& c6s, void update_grid_index_and_fraction(const std::vector<Vec3>& atomCoordinates, const Vec3 recipBoxVectors[3]);
const OpenMM::Vec3 periodicBoxVectors[3], void update_bsplines();
double* energy); void grid_spread_charge(const std::vector<double>& charges);
void pme_reciprocal_convolution(const Vec3 periodicBoxVectors[3], const Vec3 recipBoxVectors[3], double& energy);
void dpme_reciprocal_convolution(const Vec3 periodicBoxVectors[3], const Vec3 recipBoxVectors[3], double& energy);
void grid_interpolate_force(const Vec3 recipBoxVectors[3], const std::vector<double>& charges, std::vector<Vec3>& forces);
void grid_interpolate_charge_derivatives(const Vec3 recipBoxVectors[3], const std::vector<double>& charges,
std::vector<double>& chargeDerivatives, const std::vector<int>& chargeIndices);
int natoms;
double ewaldcoeff;
std::vector<std::complex<double> > grid; /* Memory for the grid we spread charges on.
* Element (i,j,k) is accessed as:
* grid[i*ngrid[1]*ngrid[2] + j*ngrid[2] + k] */
int ngrid[3]; /* Total grid dimensions (all data is complex!) */
int order; /* PME interpolation order. Almost always 4 */
/* Data for bspline interpolation, see the Essman PME paper */
std::vector<double> bsplines_moduli[3]; /* 3 pointers, to x/y/z bspline moduli, each of length ngrid[x/y/z] */
std::vector<double> bsplines_theta[3]; /* each of x/y/z has length order*natoms */
std::vector<double> bsplines_dtheta[3]; /* each of x/y/z has length order*natoms */
std::vector<std::array<int, 3> > particleindex; /* Array of length natoms. Each element is
* three ints that specify the grid
* indices for that particular atom. Updated every step! */
std::vector<Vec3> particlefraction; /* Array of length natoms. Fractional offset in the grid for
* each atom in all three dimensions. */
/* Further explanation of index/fraction:
*
* Assume we have a cell of size 10*10*10nm, and a total grid dimension of 100*100*100 cells.
* In other words, each cell is 0.1*0.1*0.1 nm.
*
* If particle i has coordinates { 0.543 , 6.235 , -0.73 }, we will get:
*
* particleindex[i] = { 5 , 62 , 92 } (-0.73 + 10 = 9.27, we always apply PBC for grid calculations!)
* particlefraction[i] = { 0.43 , 0.35 , 0.7 } (this is the fraction of the cell length where the atom is)
*
* (The reason for precaculating / storing these is that it gets a bit more complex for triclinic cells :-)
*
* In the current code version we might assume that a particle is not more than a whole box length away from
* the central cell, i.e., in this case we would assume all coordinates fall in -10 nm < x,y,z < 20 nm.
*/
/* Release all memory in pme structure */ double epsilon_r; /* Dielectric coefficient to use, typically 1.0 */
int OPENMM_EXPORT };
pme_destroy(pme_t pme);
} // namespace OpenMM } // namespace OpenMM
......
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
#include "ReferenceConstantPotential.h" #include "ReferenceConstantPotential.h"
#include "ReferenceForce.h" #include "ReferenceForce.h"
#include "ReferencePME.h"
#include "SimTKOpenMMUtilities.h" #include "SimTKOpenMMUtilities.h"
#include "openmm/OpenMMException.h" #include "openmm/OpenMMException.h"
#include "openmm/internal/MSVC_erfc.h" #include "openmm/internal/MSVC_erfc.h"
...@@ -97,22 +96,21 @@ void ReferenceConstantPotentialMatrixSolver::update( ...@@ -97,22 +96,21 @@ void ReferenceConstantPotentialMatrixSolver::update(
std::vector<double> dUdQ0(numElectrodeParticles); std::vector<double> dUdQ0(numElectrodeParticles);
std::vector<double> dUdQ(numElectrodeParticles); std::vector<double> dUdQ(numElectrodeParticles);
pme_t pmeData; ReferencePME pme(conp.ewaldAlpha, numParticles, conp.gridSize, 5, 1);
pme_init(&pmeData, conp.ewaldAlpha, numParticles, conp.gridSize, 5, 1);
// Get derivatives when all electrode charges are zeroed. // Get derivatives when all electrode charges are zeroed.
std::vector<double> q(charges); std::vector<double> q(charges);
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
q[elecToSys[ii]] = 0.0; q[elecToSys[ii]] = 0.0;
} }
conp.getDerivatives(numParticles, numElectrodeParticles, posData, q, exclusions, sysToElec, elecToSys, electrodeParamArray, dUdQ0, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, q, exclusions, sysToElec, elecToSys, electrodeParamArray, dUdQ0, pme);
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
int i = elecToSys[ii]; int i = elecToSys[ii];
// Get derivatives when one electrode charge is set. // Get derivatives when one electrode charge is set.
q[i] = 1.0; q[i] = 1.0;
conp.getDerivatives(numParticles, numElectrodeParticles, posData, q, exclusions, sysToElec, elecToSys, electrodeParamArray, dUdQ, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, q, exclusions, sysToElec, elecToSys, electrodeParamArray, dUdQ, pme);
q[i] = 0.0; q[i] = 0.0;
// Set matrix elements, subtracting zero charge derivatives so that the // Set matrix elements, subtracting zero charge derivatives so that the
...@@ -123,8 +121,6 @@ void ReferenceConstantPotentialMatrixSolver::update( ...@@ -123,8 +121,6 @@ void ReferenceConstantPotentialMatrixSolver::update(
A[ii][ii] = dUdQ[ii] - dUdQ0[ii]; A[ii][ii] = dUdQ[ii] - dUdQ0[ii];
} }
pme_destroy(pmeData);
// Compute Cholesky decomposition representation of the inverse. // Compute Cholesky decomposition representation of the inverse.
capacitance = JAMA::Cholesky<double>(A); capacitance = JAMA::Cholesky<double>(A);
if (!capacitance.is_spd()) { if (!capacitance.is_spd()) {
...@@ -162,7 +158,7 @@ void ReferenceConstantPotentialMatrixSolver::solve( ...@@ -162,7 +158,7 @@ void ReferenceConstantPotentialMatrixSolver::solve(
const std::vector<int>& elecToSys, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotential& conp, ReferenceConstantPotential& conp,
pme_t pmeData ReferencePME& pme
) { ) {
// Solves for charges using the matrix method. // Solves for charges using the matrix method.
...@@ -171,7 +167,7 @@ void ReferenceConstantPotentialMatrixSolver::solve( ...@@ -171,7 +167,7 @@ void ReferenceConstantPotentialMatrixSolver::solve(
charges[elecToSys[ii]] = 0.0; charges[elecToSys[ii]] = 0.0;
} }
std::vector<double> b(numElectrodeParticles); std::vector<double> b(numElectrodeParticles);
conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, b, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, b, pme);
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
b[ii] = -b[ii]; b[ii] = -b[ii];
} }
...@@ -236,14 +232,12 @@ void ReferenceConstantPotentialCGSolver::update( ...@@ -236,14 +232,12 @@ void ReferenceConstantPotentialCGSolver::update(
// calculation. This will actually vary slightly with position but only due // calculation. This will actually vary slightly with position but only due
// to finite accuracy of the PME splines, so it is fine to assume it will be // to finite accuracy of the PME splines, so it is fine to assume it will be
// constant for the preconditioner. // constant for the preconditioner.
pme_t pmeData; ReferencePME pme(conp.ewaldAlpha, 1, conp.gridSize, 5, 1);
pme_init(&pmeData, conp.ewaldAlpha, 1, conp.gridSize, 5, 1);
std::vector<Vec3> pmePosData(1); std::vector<Vec3> pmePosData(1);
std::vector<double> pmeChargeDerivatives(1); std::vector<double> pmeChargeDerivatives(1);
std::vector<int> pmeElectrodeIndices(1); std::vector<int> pmeElectrodeIndices(1);
std::vector<double> pmeCharges(1, 1.0); std::vector<double> pmeCharges(1, 1.0);
pme_exec_charge_derivatives(pmeData, pmePosData, pmeChargeDerivatives, pmeElectrodeIndices, pmeCharges, boxVectors); pme.exec_charge_derivatives(pmePosData, pmeChargeDerivatives, pmeElectrodeIndices, pmeCharges, boxVectors);
pme_destroy(pmeData);
// The diagonal has a contribution from reciprocal space, Ewald // The diagonal has a contribution from reciprocal space, Ewald
// self-interaction, Ewald neutralizing plasma, Gaussian self-interaction, // self-interaction, Ewald neutralizing plasma, Gaussian self-interaction,
...@@ -274,7 +268,7 @@ void ReferenceConstantPotentialCGSolver::solve( ...@@ -274,7 +268,7 @@ void ReferenceConstantPotentialCGSolver::solve(
const std::vector<int>& elecToSys, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotential& conp, ReferenceConstantPotential& conp,
pme_t pmeData ReferencePME& pme
) { ) {
// Solves for charges using the conjugate gradient method. // Solves for charges using the conjugate gradient method.
...@@ -302,7 +296,7 @@ void ReferenceConstantPotentialCGSolver::solve( ...@@ -302,7 +296,7 @@ void ReferenceConstantPotentialCGSolver::solve(
} }
// Evaluate the initial gradient Aq - b. // Evaluate the initial gradient Aq - b.
conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad, pme);
// Project the initial gradient without preconditioning. // Project the initial gradient without preconditioning.
offset = 0.0; offset = 0.0;
...@@ -332,7 +326,7 @@ void ReferenceConstantPotentialCGSolver::solve( ...@@ -332,7 +326,7 @@ void ReferenceConstantPotentialCGSolver::solve(
q[ii] = charges[i]; q[ii] = charges[i];
charges[i] = 0.0; charges[i] = 0.0;
} }
conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad0, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad0, pme);
// Project the initial gradient with preconditioning. // Project the initial gradient with preconditioning.
if (precond) { if (precond) {
...@@ -362,7 +356,7 @@ void ReferenceConstantPotentialCGSolver::solve( ...@@ -362,7 +356,7 @@ void ReferenceConstantPotentialCGSolver::solve(
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
charges[elecToSys[ii]] = qStep[ii]; charges[elecToSys[ii]] = qStep[ii];
} }
conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, gradStep, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, gradStep, pme);
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
gradStep[ii] -= grad0[ii]; gradStep[ii] -= grad0[ii];
} }
...@@ -417,7 +411,7 @@ void ReferenceConstantPotentialCGSolver::solve( ...@@ -417,7 +411,7 @@ void ReferenceConstantPotentialCGSolver::solve(
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
charges[elecToSys[ii]] = q[ii]; charges[elecToSys[ii]] = q[ii];
} }
conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad, pmeData); conp.getDerivatives(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, grad, pme);
} }
else { else {
for (int ii = 0; ii < numElectrodeParticles; ii++) { for (int ii = 0; ii < numElectrodeParticles; ii++) {
...@@ -542,13 +536,9 @@ void ReferenceConstantPotential::execute( ...@@ -542,13 +536,9 @@ void ReferenceConstantPotential::execute(
double* energy, double* energy,
ReferenceConstantPotentialSolver* solver ReferenceConstantPotentialSolver* solver
) { ) {
pme_t pmeData; ReferencePME pme(ewaldAlpha, numParticles, gridSize, 5, 1);
pme_init(&pmeData, ewaldAlpha, numParticles, gridSize, 5, 1); solver->solve(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, *this, pme);
getEnergyForces(numParticles, numElectrodeParticles, posData, forceData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, energy, pme);
solver->solve(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, *this, pmeData);
getEnergyForces(numParticles, numElectrodeParticles, posData, forceData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, energy, pmeData);
pme_destroy(pmeData);
} }
void ReferenceConstantPotential::getCharges( void ReferenceConstantPotential::getCharges(
...@@ -562,12 +552,8 @@ void ReferenceConstantPotential::getCharges( ...@@ -562,12 +552,8 @@ void ReferenceConstantPotential::getCharges(
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
ReferenceConstantPotentialSolver* solver ReferenceConstantPotentialSolver* solver
) { ) {
pme_t pmeData; ReferencePME pme(ewaldAlpha, numParticles, gridSize, 5, 1);
pme_init(&pmeData, ewaldAlpha, numParticles, gridSize, 5, 1); solver->solve(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, *this, pme);
solver->solve(numParticles, numElectrodeParticles, posData, charges, exclusions, sysToElec, elecToSys, electrodeParamArray, *this, pmeData);
pme_destroy(pmeData);
} }
void ReferenceConstantPotential::getEnergyForces( void ReferenceConstantPotential::getEnergyForces(
...@@ -581,7 +567,7 @@ void ReferenceConstantPotential::getEnergyForces( ...@@ -581,7 +567,7 @@ void ReferenceConstantPotential::getEnergyForces(
const std::vector<int>& elecToSys, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
double* energy, double* energy,
pme_t pmeData ReferencePME& pme
) { ) {
const double SQRT_PI = sqrt(PI_M); const double SQRT_PI = sqrt(PI_M);
const double TWO_OVER_SQRT_PI = 2.0 / SQRT_PI; const double TWO_OVER_SQRT_PI = 2.0 / SQRT_PI;
...@@ -671,7 +657,7 @@ void ReferenceConstantPotential::getEnergyForces( ...@@ -671,7 +657,7 @@ void ReferenceConstantPotential::getEnergyForces(
// Reciprocal space. // Reciprocal space.
double pmeEnergy = 0.0; double pmeEnergy = 0.0;
pme_exec(pmeData, posData, forceData, charges, boxVectors, &pmeEnergy); pme.exec(posData, forceData, charges, boxVectors, pmeEnergy);
energyAccum += pmeEnergy; energyAccum += pmeEnergy;
// Ewald self-interaction and external field contributions (all particles). // Ewald self-interaction and external field contributions (all particles).
...@@ -710,7 +696,7 @@ void ReferenceConstantPotential::getDerivatives( ...@@ -710,7 +696,7 @@ void ReferenceConstantPotential::getDerivatives(
const std::vector<int>& elecToSys, const std::vector<int>& elecToSys,
const std::vector<std::array<double, 3> >& electrodeParamArray, const std::vector<std::array<double, 3> >& electrodeParamArray,
std::vector<double>& chargeDerivatives, std::vector<double>& chargeDerivatives,
pme_t pmeData ReferencePME& pme
) { ) {
const double SQRT_PI = sqrt(PI_M); const double SQRT_PI = sqrt(PI_M);
const double SELF_ALPHA_SCALE = 2.0 * ONE_4PI_EPS0 / SQRT_PI; const double SELF_ALPHA_SCALE = 2.0 * ONE_4PI_EPS0 / SQRT_PI;
...@@ -757,7 +743,7 @@ void ReferenceConstantPotential::getDerivatives( ...@@ -757,7 +743,7 @@ void ReferenceConstantPotential::getDerivatives(
} }
// Reciprocal space. // Reciprocal space.
pme_exec_charge_derivatives(pmeData, posData, chargeDerivatives, elecToSys, charges, boxVectors); pme.exec_charge_derivatives(posData, chargeDerivatives, elecToSys, charges, boxVectors);
// Ewald neutralizing plasma precalculation. // Ewald neutralizing plasma precalculation.
double qTotal = 0.0; double qTotal = 0.0;
......
...@@ -243,33 +243,28 @@ void ReferenceLJCoulombIxn::calculateEwaldIxn(int numberOfAtoms, vector<Vec3>& a ...@@ -243,33 +243,28 @@ void ReferenceLJCoulombIxn::calculateEwaldIxn(int numberOfAtoms, vector<Vec3>& a
// PME // PME
if (pme && includeReciprocal) { if (pme && includeReciprocal) {
pme_t pmedata; /* abstract handle for PME data */ ReferencePME pme(alphaEwald,numberOfAtoms,meshDim,5,1);
pme_init(&pmedata,alphaEwald,numberOfAtoms,meshDim,5,1);
vector<double> charges(numberOfAtoms); vector<double> charges(numberOfAtoms);
for (int i = 0; i < numberOfAtoms; i++) for (int i = 0; i < numberOfAtoms; i++)
charges[i] = atomParameters[i][QIndex]; charges[i] = atomParameters[i][QIndex];
pme_exec(pmedata,atomCoordinates,forces,charges,periodicBoxVectors,&recipEnergy); pme.exec(atomCoordinates, forces, charges, periodicBoxVectors, recipEnergy);
if (totalEnergy) if (totalEnergy)
*totalEnergy += recipEnergy; *totalEnergy += recipEnergy;
pme_destroy(pmedata);
if (ljpme) { if (ljpme) {
// Dispersion reciprocal space terms // Dispersion reciprocal space terms
pme_init(&pmedata,alphaDispersionEwald,numberOfAtoms,dispersionMeshDim,5,1); ReferencePME ljpme(alphaDispersionEwald,numberOfAtoms,dispersionMeshDim,5,1);
std::vector<Vec3> dpmeforces(numberOfAtoms); std::vector<Vec3> dpmeforces(numberOfAtoms);
for (int i = 0; i < numberOfAtoms; i++) for (int i = 0; i < numberOfAtoms; i++)
charges[i] = 8.0*pow(atomParameters[i][SigIndex], 3.0) * atomParameters[i][EpsIndex]; charges[i] = 8.0*pow(atomParameters[i][SigIndex], 3.0) * atomParameters[i][EpsIndex];
pme_exec_dpme(pmedata,atomCoordinates,dpmeforces,charges,periodicBoxVectors,&recipDispersionEnergy); ljpme.exec_dpme(atomCoordinates, dpmeforces, charges, periodicBoxVectors, recipDispersionEnergy);
for (int i = 0; i < numberOfAtoms; i++) for (int i = 0; i < numberOfAtoms; i++)
forces[i] += dpmeforces[i]; forces[i] += dpmeforces[i];
if (totalEnergy) if (totalEnergy)
*totalEnergy += recipDispersionEnergy; *totalEnergy += recipDispersionEnergy;
pme_destroy(pmedata);
} }
} }
// Ewald method // Ewald method
......
...@@ -29,10 +29,8 @@ ...@@ -29,10 +29,8 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <math.h> #include <cmath>
#include <stdio.h> #include <vector>
#include <stdlib.h>
#include <complex>
#include "ReferencePME.h" #include "ReferencePME.h"
#include "ReferenceForce.h" #include "ReferenceForce.h"
...@@ -45,171 +43,72 @@ ...@@ -45,171 +43,72 @@
using namespace std; using namespace std;
typedef int ivec[3];
namespace OpenMM { namespace OpenMM {
struct pme /* Only called once from constructor, performance does not matter! */
{ void ReferencePME::calculate_bsplines_moduli() {
int natoms; int nmax = 0;
double ewaldcoeff; for (int d = 0; d < 3; d++) {
nmax = (ngrid[d] > nmax) ? ngrid[d] : nmax;
complex<double>* grid; /* Memory for the grid we spread charges on. bsplines_moduli[d].resize(ngrid[d]);
* Element (i,j,k) is accessed as:
* grid[i*ngrid[1]*ngrid[2] + j*ngrid[2] + k]
*/
int ngrid[3]; /* Total grid dimensions (all data is complex!) */
int order; /* PME interpolation order. Almost always 4 */
/* Data for bspline interpolation, see the Essman PME paper */
double * bsplines_moduli[3]; /* 3 pointers, to x/y/z bspline moduli, each of length ngrid[x/y/z] */
double * bsplines_theta[3]; /* each of x/y/z has length order*natoms */
double * bsplines_dtheta[3]; /* each of x/y/z has length order*natoms */
ivec * particleindex; /* Array of length natoms. Each element is
* an ivec (3 ints) that specify the grid
* indices for that particular atom. Updated every step!
*/
rvec * particlefraction; /* Array of length natoms. Fractional offset in the grid for
* each atom in all three dimensions.
*/
/* Further explanation of index/fraction:
*
* Assume we have a cell of size 10*10*10nm, and a total grid dimension of 100*100*100 cells.
* In other words, each cell is 0.1*0.1*0.1 nm.
*
* If particle i has coordinates { 0.543 , 6.235 , -0.73 }, we will get:
*
* particleindex[i] = { 5 , 62 , 92 } (-0.73 + 10 = 9.27, we always apply PBC for grid calculations!)
* particlefraction[i] = { 0.43 , 0.35 , 0.7 } (this is the fraction of the cell length where the atom is)
*
* (The reason for precaculating / storing these is that it gets a bit more complex for triclinic cells :-)
*
* In the current code version we might assume that a particle is not more than a whole box length away from
* the central cell, i.e., in this case we would assume all coordinates fall in -10 nm < x,y,z < 20 nm.
*/
double epsilon_r; /* Dielectric coefficient to use, typically 1.0 */
};
/* Internal setup routines */
/* Only called once from init_pme(), performance does not matter! */
static void
pme_calculate_bsplines_moduli(pme_t pme)
{
int nmax;
int i,j,k,l,d;
int order;
int ndata;
double * data;
double * ddata;
double * bsplines_data;
double div;
double sc,ss,arg;
nmax = 0;
for (d=0;d<3;d++)
{
nmax = (pme->ngrid[d] > nmax) ? pme->ngrid[d] : nmax;
pme->bsplines_moduli[d] = (double *) malloc(sizeof(double)*pme->ngrid[d]);
} }
order = pme->order;
/* temp storage in this routine */ /* temp storage in this routine */
data = (double *) malloc(sizeof(double)*order); vector<double> data(order);
ddata = (double *) malloc(sizeof(double)*order); vector<double> ddata(order);
bsplines_data = (double *) malloc(sizeof(double)*nmax); vector<double> bsplines_data(nmax);
data[order-1]=0; data[order-1]=0;
data[1]=0; data[1]=0;
data[0]=1; data[0]=1;
for (k=3;k<order;k++) for (int k = 3; k < order; k++) {
{ double div=1.0/(k-1.0);
div=1.0/(k-1.0);
data[k-1]=0; data[k-1]=0;
for (l=1;l<(k-1);l++) for (int l = 1; l < (k-1); l++)
{
data[k-l-1]=div*(l*data[k-l-2]+(k-l)*data[k-l-1]); data[k-l-1]=div*(l*data[k-l-2]+(k-l)*data[k-l-1]);
}
data[0]=div*data[0]; data[0]=div*data[0];
} }
/* differentiate */ /* differentiate */
ddata[0]=-data[0]; ddata[0]=-data[0];
for (k=1;k<order;k++) for (int k = 1 ; k < order; k++)
{
ddata[k]=data[k-1]-data[k]; ddata[k]=data[k-1]-data[k];
}
div=1.0/(order-1); double div=1.0/(order-1);
data[order-1]=0; data[order-1]=0;
for (l=1;l<(order-1);l++) for (int l = 1; l < (order-1); l++)
{
data[order-l-1]=div*(l*data[order-l-2]+(order-l)*data[order-l-1]); data[order-l-1]=div*(l*data[order-l-2]+(order-l)*data[order-l-1]);
}
data[0]=div*data[0]; data[0]=div*data[0];
for (i=0;i<nmax;i++) for (int i = 0; i < nmax; i++)
{
bsplines_data[i]=0; bsplines_data[i]=0;
} for (int i = 1; i <= order; i++)
for (i=1;i<=order;i++)
{
bsplines_data[i]=data[i-1]; bsplines_data[i]=data[i-1];
}
/* Evaluate the actual bspline moduli for X/Y/Z */ /* Evaluate the actual bspline moduli for X/Y/Z */
for (d=0;d<3;d++) for (int d = 0; d < 3; d++) {
{ int ndata = ngrid[d];
ndata = pme->ngrid[d]; for (int i = 0; i < ndata; i++) {
for (i=0;i<ndata;i++) double sc = 0;
{ double ss = 0;
sc=ss=0; for (int j = 0; j < ndata; j++) {
for (j=0;j<ndata;j++) double arg=(2.0*M_PI*i*j)/ndata;
{
arg=(2.0*M_PI*i*j)/ndata;
sc+=bsplines_data[j]*cos(arg); sc+=bsplines_data[j]*cos(arg);
ss+=bsplines_data[j]*sin(arg); ss+=bsplines_data[j]*sin(arg);
} }
pme->bsplines_moduli[d][i]=sc*sc+ss*ss; bsplines_moduli[d][i]=sc*sc+ss*ss;
}
for (i=0;i<ndata;i++)
{
if (pme->bsplines_moduli[d][i]<1.0e-7)
{
pme->bsplines_moduli[d][i]=(pme->bsplines_moduli[d][(i-1+ndata)%ndata]+pme->bsplines_moduli[d][(i+1)%ndata])/2;
} }
for (int i = 0; i < ndata; i++) {
if (bsplines_moduli[d][i]<1.0e-7)
bsplines_moduli[d][i]=(bsplines_moduli[d][(i-1+ndata)%ndata]+bsplines_moduli[d][(i+1)%ndata])/2;
} }
} }
/* Release temp storage */
free(data);
free(ddata);
free(bsplines_data);
} }
static void void ReferencePME::update_grid_index_and_fraction(const vector<Vec3>& atomCoordinates, const Vec3 recipBoxVectors[3]) {
pme_update_grid_index_and_fraction(pme_t pme, for (int i = 0; i < natoms; i++) {
const vector<Vec3>& atomCoordinates,
const Vec3 periodicBoxVectors[3],
const Vec3 recipBoxVectors[3])
{
int i;
int d;
double t;
int ti;
for (i=0;i<pme->natoms;i++)
{
/* Index calculation (Look mom, no conditionals!): /* Index calculation (Look mom, no conditionals!):
* *
* Both for Cuda and modern CPUs it is nice to avoid conditionals, but we still need to apply periodic boundary conditions. * Both for Cuda and modern CPUs it is nice to avoid conditionals, but we still need to apply periodic boundary conditions.
...@@ -247,14 +146,13 @@ pme_update_grid_index_and_fraction(pme_t pme, ...@@ -247,14 +146,13 @@ pme_update_grid_index_and_fraction(pme_t pme,
* (And, by adding 100.0 box lengths, we would lose a bit of numerical accuracy here!) * (And, by adding 100.0 box lengths, we would lose a bit of numerical accuracy here!)
*/ */
Vec3 coord = atomCoordinates[i]; Vec3 coord = atomCoordinates[i];
for (d=0;d<3;d++) for (int d = 0; d < 3; d++) {
{ double t = coord[0]*recipBoxVectors[0][d]+coord[1]*recipBoxVectors[1][d]+coord[2]*recipBoxVectors[2][d];
t = coord[0]*recipBoxVectors[0][d]+coord[1]*recipBoxVectors[1][d]+coord[2]*recipBoxVectors[2][d]; t = (t-floor(t))*ngrid[d];
t = (t-floor(t))*pme->ngrid[d]; int ti = (int) t;
ti = (int) t;
particlefraction[i][d] = t - ti;
pme->particlefraction[i][d] = t - ti; particleindex[i][d] = ti % ngrid[d];
pme->particleindex[i][d] = ti % pme->ngrid[d];
} }
} }
} }
...@@ -265,97 +163,60 @@ pme_update_grid_index_and_fraction(pme_t pme, ...@@ -265,97 +163,60 @@ pme_update_grid_index_and_fraction(pme_t pme,
* *
* In practice, it might help to require order=4 for the cuda port. * In practice, it might help to require order=4 for the cuda port.
*/ */
static void void ReferencePME::update_bsplines() {
pme_update_bsplines(pme_t pme) for (int i = 0; i < natoms; i++) {
{ for (int j = 0; j < 3; j++) {
int i,j,k,l;
int order;
double dr,div;
double * data;
double * ddata;
order = pme->order;
for (i=0; (i<pme->natoms); i++)
{
for (j=0; j<3; j++)
{
/* dr is relative offset from lower cell limit */ /* dr is relative offset from lower cell limit */
dr = pme->particlefraction[i][j]; double dr = particlefraction[i][j];
data = &(pme->bsplines_theta[j][i*order]); double* data = &(bsplines_theta[j][i*order]);
ddata = &(pme->bsplines_dtheta[j][i*order]); double* ddata = &(bsplines_dtheta[j][i*order]);
data[order-1] = 0; data[order-1] = 0;
data[1] = dr; data[1] = dr;
data[0] = 1-dr; data[0] = 1-dr;
for (k=3; k<order; k++) for (int k = 3; k < order; k++) {
{ double div = 1.0/(k-1.0);
div = 1.0/(k-1.0);
data[k-1] = div*dr*data[k-2]; data[k-1] = div*dr*data[k-2];
for (l=1; l<(k-1); l++) for (int l = 1; l < (k-1); l++)
{
data[k-l-1] = div*((dr+l)*data[k-l-2]+(k-l-dr)*data[k-l-1]); data[k-l-1] = div*((dr+l)*data[k-l-2]+(k-l-dr)*data[k-l-1]);
}
data[0] = div*(1-dr)*data[0]; data[0] = div*(1-dr)*data[0];
} }
/* differentiate */ /* differentiate */
ddata[0] = -data[0]; ddata[0] = -data[0];
for (k=1; k<order; k++) for (int k = 1; k < order; k++)
{
ddata[k] = data[k-1]-data[k]; ddata[k] = data[k-1]-data[k];
}
div = 1.0/(order-1); double div = 1.0/(order-1);
data[order-1] = div*dr*data[order-2]; data[order-1] = div*dr*data[order-2];
for (l=1; l<(order-1); l++) for (int l = 1; l < (order-1); l++)
{
data[order-l-1] = div*((dr+l)*data[order-l-2]+(order-l-dr)*data[order-l-1]); data[order-l-1] = div*((dr+l)*data[order-l-2]+(order-l-dr)*data[order-l-1]);
}
data[0] = div*(1-dr)*data[0]; data[0] = div*(1-dr)*data[0];
} }
} }
} }
static void void ReferencePME::grid_spread_charge(const vector<double>& charges) {
pme_grid_spread_charge(pme_t pme, const vector<double>& charges)
{
int order;
int i;
int ix,iy,iz;
int x0index,y0index,z0index;
int xindex,yindex,zindex;
int index;
double q;
double * thetax;
double * thetay;
double * thetaz;
order = pme->order;
/* Reset the grid */ /* Reset the grid */
for (i=0;i<pme->ngrid[0]*pme->ngrid[1]*pme->ngrid[2];i++) for (int i = 0; i < ngrid[0]*ngrid[1]*ngrid[2]; i++)
{ grid[i] = complex<double>(0, 0);
pme->grid[i] = complex<double>(0, 0);
}
for (i=0;i<pme->natoms;i++) for (int i = 0; i < natoms; i++) {
{ double q = charges[i];
q = charges[i];
/* Grid index for the actual atom position */ /* Grid index for the actual atom position */
x0index = pme->particleindex[i][0]; int x0index = particleindex[i][0];
y0index = pme->particleindex[i][1]; int y0index = particleindex[i][1];
z0index = pme->particleindex[i][2]; int z0index = particleindex[i][2];
/* Bspline factors for this atom in each dimension , calculated from fractional coordinates */ /* Bspline factors for this atom in each dimension , calculated from fractional coordinates */
thetax = &(pme->bsplines_theta[0][i*order]); double* thetax = &(bsplines_theta[0][i*order]);
thetay = &(pme->bsplines_theta[1][i*order]); double* thetay = &(bsplines_theta[1][i*order]);
thetaz = &(pme->bsplines_theta[2][i*order]); double* thetaz = &(bsplines_theta[2][i*order]);
/* Loop over norder*norder*norder (typically 5*5*5) neighbor cells. /* Loop over norder*norder*norder (typically 5*5*5) neighbor cells.
* *
...@@ -375,23 +236,20 @@ pme_grid_spread_charge(pme_t pme, const vector<double>& charges) ...@@ -375,23 +236,20 @@ pme_grid_spread_charge(pme_t pme, const vector<double>& charges)
* 3) When we parallelize things, we only need to communicate in one direction instead of two! * 3) When we parallelize things, we only need to communicate in one direction instead of two!
*/ */
for (ix=0;ix<order;ix++) for (int ix = 0; ix < order; ix++) {
{
/* Calculate index, apply PBC so we spread to index 0/1/2 when a particle is close to the upper limit of the grid */ /* Calculate index, apply PBC so we spread to index 0/1/2 when a particle is close to the upper limit of the grid */
xindex = (x0index + ix) % pme->ngrid[0]; int xindex = (x0index + ix) % ngrid[0];
for (iy=0;iy<order;iy++) for (int iy = 0; iy < order; iy++) {
{ int yindex = (y0index + iy) % ngrid[1];
yindex = (y0index + iy) % pme->ngrid[1];
for (iz=0;iz<order;iz++) for (int iz = 0; iz < order; iz++) {
{
/* Can be optimized, but we keep it simple here */ /* Can be optimized, but we keep it simple here */
zindex = (z0index + iz) % pme->ngrid[2]; int zindex = (z0index + iz) % ngrid[2];
/* Calculate index in the charge grid */ /* Calculate index in the charge grid */
index = xindex*pme->ngrid[1]*pme->ngrid[2] + yindex*pme->ngrid[2] + zindex; int index = xindex*ngrid[1]*ngrid[2] + yindex*ngrid[2] + zindex;
/* Add the charge times the bspline spread/interpolation factors to this grid position */ /* Add the charge times the bspline spread/interpolation factors to this grid position */
pme->grid[index] += q*thetax[ix]*thetay[iy]*thetaz[iz]; grid[index] += q*thetax[ix]*thetay[iy]*thetaz[iz];
} }
} }
} }
...@@ -400,67 +258,36 @@ pme_grid_spread_charge(pme_t pme, const vector<double>& charges) ...@@ -400,67 +258,36 @@ pme_grid_spread_charge(pme_t pme, const vector<double>& charges)
static void void ReferencePME::pme_reciprocal_convolution(const Vec3 periodicBoxVectors[3], const Vec3 recipBoxVectors[3], double& energy) {
pme_reciprocal_convolution(pme_t pme, int nx = ngrid[0];
const Vec3 periodicBoxVectors[3], int ny = ngrid[1];
const Vec3 recipBoxVectors[3], int nz = ngrid[2];
double * energy)
{ double one_4pi_eps = ONE_4PI_EPS0/epsilon_r;
int kx,ky,kz; double factor = M_PI*M_PI/(ewaldcoeff*ewaldcoeff);
int nx,ny,nz; double boxfactor = M_PI*periodicBoxVectors[0][0]*periodicBoxVectors[1][1]*periodicBoxVectors[2][2];
double mx,my,mz;
double mhx,mhy,mhz,m2; double esum = 0;
double one_4pi_eps;
double virxx,virxy,virxz,viryy,viryz,virzz; double maxkx = (nx+1)/2;
double bx,by,bz; double maxky = (ny+1)/2;
double d1,d2; double maxkz = (nz+1)/2;
double eterm,vfactor,struct2,ets2;
double esum; for (int kx = 0; kx < nx; kx++) {
double factor;
double denom;
double boxfactor;
double maxkx,maxky,maxkz;
complex<double> *ptr;
nx = pme->ngrid[0];
ny = pme->ngrid[1];
nz = pme->ngrid[2];
one_4pi_eps = ONE_4PI_EPS0/pme->epsilon_r;
factor = M_PI*M_PI/(pme->ewaldcoeff*pme->ewaldcoeff);
boxfactor = M_PI*periodicBoxVectors[0][0]*periodicBoxVectors[1][1]*periodicBoxVectors[2][2];
esum = 0;
virxx = 0;
virxy = 0;
virxz = 0;
viryy = 0;
viryz = 0;
virzz = 0;
maxkx = (nx+1)/2;
maxky = (ny+1)/2;
maxkz = (nz+1)/2;
for (kx=0;kx<nx;kx++)
{
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
mx = (kx<maxkx) ? kx : (kx-nx); double mx = (kx<maxkx) ? kx : (kx-nx);
mhx = mx*recipBoxVectors[0][0]; double mhx = mx*recipBoxVectors[0][0];
bx = boxfactor*pme->bsplines_moduli[0][kx]; double bx = boxfactor*bsplines_moduli[0][kx];
for (ky=0;ky<ny;ky++) for (int ky = 0; ky < ny; ky++) {
{
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
my = (ky<maxky) ? ky : (ky-ny); double my = (ky<maxky) ? ky : (ky-ny);
mhy = mx*recipBoxVectors[1][0]+my*recipBoxVectors[1][1]; double mhy = mx*recipBoxVectors[1][0]+my*recipBoxVectors[1][1];
by = pme->bsplines_moduli[1][ky]; double by = bsplines_moduli[1][ky];
for (kz=0;kz<nz;kz++) for (int kz = 0; kz < nz; kz++) {
{
/* Pointer to the grid cell in question */ /* Pointer to the grid cell in question */
ptr = pme->grid + kx*ny*nz + ky*nz + kz; complex<double>& ptr = grid[kx*ny*nz + ky*nz + kz];
/* The zero frequency term is undefined due to division by the frequency below. Set this term to zero; /* The zero frequency term is undefined due to division by the frequency below. Set this term to zero;
* in the case that the net charge of the system is non-zero, this is equivalent to applying a uniform * in the case that the net charge of the system is non-zero, this is equivalent to applying a uniform
...@@ -468,228 +295,170 @@ pme_reciprocal_convolution(pme_t pme, ...@@ -468,228 +295,170 @@ pme_reciprocal_convolution(pme_t pme,
* this neutralizing plasma is applied elsewhere. If this term is not zeroed, however, energies and * this neutralizing plasma is applied elsewhere. If this term is not zeroed, however, energies and
* forces will be unaffected but charge derivatives for non-neutral systems will be incorrect! * forces will be unaffected but charge derivatives for non-neutral systems will be incorrect!
*/ */
if (kx==0 && ky==0 && kz==0) if (kx==0 && ky==0 && kz==0) {
{ ptr = 0;
*ptr = 0;
continue; continue;
} }
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
mz = (kz<maxkz) ? kz : (kz-nz); double mz = (kz<maxkz) ? kz : (kz-nz);
mhz = mx*recipBoxVectors[2][0]+my*recipBoxVectors[2][1]+mz*recipBoxVectors[2][2]; double mhz = mx*recipBoxVectors[2][0]+my*recipBoxVectors[2][1]+mz*recipBoxVectors[2][2];
/* Get grid data for this frequency */ /* Get grid data for this frequency */
d1 = ptr->real(); double d1 = ptr.real();
d2 = ptr->imag(); double d2 = ptr.imag();
/* Calculate the convolution - see the Essman/Darden paper for the equation! */ /* Calculate the convolution - see the Essman/Darden paper for the equation! */
m2 = mhx*mhx+mhy*mhy+mhz*mhz; double m2 = mhx*mhx+mhy*mhy+mhz*mhz;
bz = pme->bsplines_moduli[2][kz]; double bz = bsplines_moduli[2][kz];
denom = m2*bx*by*bz; double denom = m2*bx*by*bz;
eterm = one_4pi_eps*exp(-factor*m2)/denom; double eterm = one_4pi_eps*exp(-factor*m2)/denom;
/* write back convolution data to grid */ /* write back convolution data to grid */
ptr->real(d1*eterm); ptr.real(d1*eterm);
ptr->imag(d2*eterm); ptr.imag(d2*eterm);
struct2 = (d1*d1+d2*d2); double struct2 = (d1*d1+d2*d2);
/* Long-range PME contribution to the energy for this frequency */ /* Long-range PME contribution to the energy for this frequency */
ets2 = eterm*struct2; double ets2 = eterm*struct2;
esum += ets2; esum += ets2;
} }
} }
} }
/* The factor 0.5 is nothing special, but it is better to have it here than inside the loop :-) */ /* The factor 0.5 is nothing special, but it is better to have it here than inside the loop :-) */
*energy = 0.5*esum; energy = 0.5*esum;
} }
static void void ReferencePME::dpme_reciprocal_convolution(const Vec3 periodicBoxVectors[3], const Vec3 recipBoxVectors[3], double& energy) {
dpme_reciprocal_convolution(pme_t pme, int nx = ngrid[0];
const Vec3 periodicBoxVectors[3], int ny = ngrid[1];
const Vec3 recipBoxVectors[3], int nz = ngrid[2];
double* energy)
{
int kx,ky,kz;
int nx,ny,nz;
double mx,my,mz;
double mhx,mhy,mhz,m2;
double bx,by,bz;
double d1,d2;
double eterm,struct2,ets2;
double esum;
double denom;
double boxfactor;
double maxkx,maxky,maxkz;
complex<double> *ptr; double boxfactor = -2*M_PI*sqrt(M_PI) / (6.0*periodicBoxVectors[0][0]*periodicBoxVectors[1][1]*periodicBoxVectors[2][2]);
nx = pme->ngrid[0]; double esum = 0;
ny = pme->ngrid[1];
nz = pme->ngrid[2];
boxfactor = -2*M_PI*sqrt(M_PI) / (6.0*periodicBoxVectors[0][0]*periodicBoxVectors[1][1]*periodicBoxVectors[2][2]); double maxkx = (nx+1)/2;
double maxky = (ny+1)/2;
double maxkz = (nz+1)/2;
esum = 0; double bfac = M_PI / ewaldcoeff;
maxkx = (nx+1)/2;
maxky = (ny+1)/2;
maxkz = (nz+1)/2;
double bfac = M_PI / pme->ewaldcoeff;
double fac1 = 2.0*M_PI*M_PI*M_PI*sqrt(M_PI); double fac1 = 2.0*M_PI*M_PI*M_PI*sqrt(M_PI);
double fac2 = pme->ewaldcoeff*pme->ewaldcoeff*pme->ewaldcoeff; double fac2 = ewaldcoeff*ewaldcoeff*ewaldcoeff;
double fac3 = -2.0*pme->ewaldcoeff*M_PI*M_PI; double fac3 = -2.0*ewaldcoeff*M_PI*M_PI;
double b, m, m3, expfac, expterm, erfcterm;
for (kx=0;kx<nx;kx++) for (int kx = 0; kx < nx; kx++) {
{
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
mx = ((kx<maxkx) ? kx : (kx-nx)); double mx = ((kx<maxkx) ? kx : (kx-nx));
mhx = mx*recipBoxVectors[0][0]; double mhx = mx*recipBoxVectors[0][0];
bx = pme->bsplines_moduli[0][kx]; double bx = bsplines_moduli[0][kx];
for (ky=0;ky<ny;ky++) for (int ky = 0; ky < ny; ky++) {
{
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
my = ((ky<maxky) ? ky : (ky-ny)); double my = ((ky<maxky) ? ky : (ky-ny));
mhy = mx*recipBoxVectors[1][0]+my*recipBoxVectors[1][1]; double mhy = mx*recipBoxVectors[1][0]+my*recipBoxVectors[1][1];
by = pme->bsplines_moduli[1][ky]; double by = bsplines_moduli[1][ky];
for (kz=0;kz<nz;kz++) for (int kz = 0; kz < nz; kz++) {
{
/* /*
* Unlike the Coulombic case, there's an m=0 term so all terms are considered here. * Unlike the Coulombic case, there's an m=0 term so all terms are considered here.
*/ */
/* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */ /* Calculate frequency. Grid indices in the upper half correspond to negative frequencies! */
mz = ((kz<maxkz) ? kz : (kz-nz)); double mz = ((kz<maxkz) ? kz : (kz-nz));
mhz = mx*recipBoxVectors[2][0]+my*recipBoxVectors[2][1]+mz*recipBoxVectors[2][2]; double mhz = mx*recipBoxVectors[2][0]+my*recipBoxVectors[2][1]+mz*recipBoxVectors[2][2];
/* Pointer to the grid cell in question */ /* Pointer to the grid cell in question */
ptr = pme->grid + kx*ny*nz + ky*nz + kz; complex<double>& ptr = grid[kx*ny*nz + ky*nz + kz];
/* Get grid data for this frequency */ /* Get grid data for this frequency */
d1 = ptr->real(); double d1 = ptr.real();
d2 = ptr->imag(); double d2 = ptr.imag();
/* Calculate the convolution - see the Essman/Darden paper for the equation! */ /* Calculate the convolution - see the Essman/Darden paper for the equation! */
m2 = mhx*mhx+mhy*mhy+mhz*mhz; double m2 = mhx*mhx+mhy*mhy+mhz*mhz;
bz = pme->bsplines_moduli[2][kz]; double bz = bsplines_moduli[2][kz];
denom = boxfactor / (bx*by*bz); double denom = boxfactor / (bx*by*bz);
m = sqrt(m2); double m = sqrt(m2);
m3 = m*m2; double m3 = m*m2;
b = bfac*m; double b = bfac*m;
expfac = -b*b; double expfac = -b*b;
erfcterm = erfc(b); double erfcterm = erfc(b);
expterm = exp(expfac); double expterm = exp(expfac);
eterm = (fac1*erfcterm*m3 + expterm*(fac2 + fac3*m2)) * denom; double eterm = (fac1*erfcterm*m3 + expterm*(fac2 + fac3*m2)) * denom;
/* write back convolution data to grid */ /* write back convolution data to grid */
ptr->real(d1*eterm); ptr.real(d1*eterm);
ptr->imag(d2*eterm); ptr.imag(d2*eterm);
struct2 = (d1*d1+d2*d2); double struct2 = (d1*d1+d2*d2);
/* Long-range PME contribution to the energy for this frequency */ /* Long-range PME contribution to the energy for this frequency */
ets2 = eterm*struct2; double ets2 = eterm*struct2;
esum += ets2; esum += ets2;
} }
} }
} }
// Remember the C6 energy is attractive, hence the negative sign. // Remember the C6 energy is attractive, hence the negative sign.
*energy = 0.5*esum; energy = 0.5*esum;
} }
static void void ReferencePME::grid_interpolate_force(const Vec3 recipBoxVectors[3], const vector<double>& charges, vector<Vec3>& forces) {
pme_grid_interpolate_force(pme_t pme,
const Vec3 recipBoxVectors[3],
const vector<double>& charges,
vector<Vec3>& forces)
{
int i;
int ix,iy,iz;
int x0index,y0index,z0index;
int xindex,yindex,zindex;
int index;
int order;
double q;
double * thetax;
double * thetay;
double * thetaz;
double * dthetax;
double * dthetay;
double * dthetaz;
double tx,ty,tz;
double dtx,dty,dtz;
double fx,fy,fz;
double gridvalue;
int nx,ny,nz;
nx = pme->ngrid[0];
ny = pme->ngrid[1];
nz = pme->ngrid[2];
order = pme->order;
/* This is almost identical to the charge spreading routine! */ /* This is almost identical to the charge spreading routine! */
for (i=0;i<pme->natoms;i++) for (int i = 0; i < natoms; i++) {
{ double fx = 0, fy = 0, fz = 0;
fx = fy = fz = 0;
q = charges[i]; double q = charges[i];
/* Grid index for the actual atom position */ /* Grid index for the actual atom position */
x0index = pme->particleindex[i][0]; int x0index = particleindex[i][0];
y0index = pme->particleindex[i][1]; int y0index = particleindex[i][1];
z0index = pme->particleindex[i][2]; int z0index = particleindex[i][2];
/* Bspline factors for this atom in each dimension , calculated from fractional coordinates */ /* Bspline factors for this atom in each dimension , calculated from fractional coordinates */
thetax = &(pme->bsplines_theta[0][i*order]); double* thetax = &(bsplines_theta[0][i*order]);
thetay = &(pme->bsplines_theta[1][i*order]); double* thetay = &(bsplines_theta[1][i*order]);
thetaz = &(pme->bsplines_theta[2][i*order]); double* thetaz = &(bsplines_theta[2][i*order]);
dthetax = &(pme->bsplines_dtheta[0][i*order]); double* dthetax = &(bsplines_dtheta[0][i*order]);
dthetay = &(pme->bsplines_dtheta[1][i*order]); double* dthetay = &(bsplines_dtheta[1][i*order]);
dthetaz = &(pme->bsplines_dtheta[2][i*order]); double* dthetaz = &(bsplines_dtheta[2][i*order]);
/* See pme_grid_spread_charge() for comments about the order here, and only interpolation in one direction */ /* See grid_spread_charge() for comments about the order here, and only interpolation in one direction */
/* Since we will add order^3 (typically 5*5*5=125) terms to the force on each particle, we use temporary fx/fy/fz /* Since we will add order^3 (typically 5*5*5=125) terms to the force on each particle, we use temporary fx/fy/fz
* variables, and only add it to memory forces[] at the end. * variables, and only add it to memory forces[] at the end.
*/ */
for (ix=0;ix<order;ix++) for (int ix = 0; ix < order; ix++) {
{ int xindex = (x0index + ix) % ngrid[0];
xindex = (x0index + ix) % pme->ngrid[0];
/* Get both the bspline factor and its derivative with respect to the x coordinate! */ /* Get both the bspline factor and its derivative with respect to the x coordinate! */
tx = thetax[ix]; double tx = thetax[ix];
dtx = dthetax[ix]; double dtx = dthetax[ix];
for (iy=0;iy<order;iy++) for (int iy = 0; iy < order; iy++) {
{ int yindex = (y0index + iy) % ngrid[1];
yindex = (y0index + iy) % pme->ngrid[1];
/* bspline + derivative wrt y */ /* bspline + derivative wrt y */
ty = thetay[iy]; double ty = thetay[iy];
dty = dthetay[iy]; double dty = dthetay[iy];
for (iz=0;iz<order;iz++) for (int iz = 0; iz < order; iz++) {
{
/* Can be optimized, but we keep it simple here */ /* Can be optimized, but we keep it simple here */
zindex = (z0index + iz) % pme->ngrid[2]; int zindex = (z0index + iz) % ngrid[2];
/* bspline + derivative wrt z */ /* bspline + derivative wrt z */
tz = thetaz[iz]; double tz = thetaz[iz];
dtz = dthetaz[iz]; double dtz = dthetaz[iz];
index = xindex*pme->ngrid[1]*pme->ngrid[2] + yindex*pme->ngrid[2] + zindex; int index = xindex*ngrid[1]*ngrid[2] + yindex*ngrid[2] + zindex;
/* Get the fft+convoluted+ifft:d data from the grid, which must be real by definition */ /* Get the fft+convoluted+ifft:d data from the grid, which must be real by definition */
/* Checking that the imaginary part is indeed zero might be a good check :-) */ /* Checking that the imaginary part is indeed zero might be a good check :-) */
gridvalue = pme->grid[index].real(); double gridvalue = grid[index].real();
/* The d component of the force is calculated by taking the derived bspline in dimension d, normal bsplines in the other two */ /* The d component of the force is calculated by taking the derived bspline in dimension d, normal bsplines in the other two */
fx += dtx*ty*tz*gridvalue; fx += dtx*ty*tz*gridvalue;
...@@ -699,90 +468,58 @@ pme_grid_interpolate_force(pme_t pme, ...@@ -699,90 +468,58 @@ pme_grid_interpolate_force(pme_t pme,
} }
} }
/* Update memory force, note that we multiply by charge and some box stuff */ /* Update memory force, note that we multiply by charge and some box stuff */
forces[i][0] -= q*(fx*nx*recipBoxVectors[0][0]); forces[i][0] -= q*(fx*ngrid[0]*recipBoxVectors[0][0]);
forces[i][1] -= q*(fx*nx*recipBoxVectors[1][0]+fy*ny*recipBoxVectors[1][1]); forces[i][1] -= q*(fx*ngrid[0]*recipBoxVectors[1][0]+fy*ngrid[1]*recipBoxVectors[1][1]);
forces[i][2] -= q*(fx*nx*recipBoxVectors[2][0]+fy*ny*recipBoxVectors[2][1]+fz*nz*recipBoxVectors[2][2]); forces[i][2] -= q*(fx*ngrid[0]*recipBoxVectors[2][0]+fy*ngrid[1]*recipBoxVectors[2][1]+fz*ngrid[2]*recipBoxVectors[2][2]);
} }
} }
static void void ReferencePME::grid_interpolate_charge_derivatives(const Vec3 recipBoxVectors[3], const vector<double>& charges,
pme_grid_interpolate_charge_derivatives(pme_t pme, vector<double>& chargeDerivatives, const vector<int>& chargeIndices) {
const Vec3 recipBoxVectors[3], /* This is similar to grid_interpolate_force() */
const vector<double>& charges,
vector<double>& chargeDerivatives, int nderiv = chargeIndices.size();
const vector<int>& chargeIndices) for (int ideriv = 0; ideriv < nderiv; ideriv++) {
{ int i = chargeIndices[ideriv];
int nderiv, ideriv, i; double dq = 0;
int ix,iy,iz;
int x0index,y0index,z0index;
int xindex,yindex,zindex;
int index;
int order;
double q;
double * thetax;
double * thetay;
double * thetaz;
double tx,ty,tz;
double dq;
double gridvalue;
int nx,ny,nz;
nx = pme->ngrid[0];
ny = pme->ngrid[1];
nz = pme->ngrid[2];
order = pme->order;
/* This is similar to pme_grid_interpolate_force() */
nderiv = chargeIndices.size();
for (ideriv=0;ideriv<nderiv;ideriv++)
{
i = chargeIndices[ideriv];
dq = 0;
q = charges[i];
/* Grid index for the actual atom position */ /* Grid index for the actual atom position */
x0index = pme->particleindex[i][0]; int x0index = particleindex[i][0];
y0index = pme->particleindex[i][1]; int y0index = particleindex[i][1];
z0index = pme->particleindex[i][2]; int z0index = particleindex[i][2];
/* Bspline factors for this atom in each dimension , calculated from fractional coordinates */ /* Bspline factors for this atom in each dimension , calculated from fractional coordinates */
thetax = &(pme->bsplines_theta[0][i*order]); double* thetax = &(bsplines_theta[0][i*order]);
thetay = &(pme->bsplines_theta[1][i*order]); double* thetay = &(bsplines_theta[1][i*order]);
thetaz = &(pme->bsplines_theta[2][i*order]); double* thetaz = &(bsplines_theta[2][i*order]);
/* See pme_grid_spread_charge() for comments about the order here, and only interpolation in one direction */ /* See grid_spread_charge() for comments about the order here, and only interpolation in one direction */
/* Since we will add order^3 (typically 5*5*5=125) terms to the charge /* Since we will add order^3 (typically 5*5*5=125) terms to the charge
* derivative on each particle, we use a temporary dq variable, and only * derivative on each particle, we use a temporary dq variable, and only
* add it to memory forces[] at the end. * add it to memory forces[] at the end.
*/ */
for (ix=0;ix<order;ix++) for (int ix = 0; ix < order; ix++) {
{ int xindex = (x0index + ix) % ngrid[0];
xindex = (x0index + ix) % pme->ngrid[0];
/* Get the bspline factor with respect to the x coordinate */ /* Get the bspline factor with respect to the x coordinate */
tx = thetax[ix]; double tx = thetax[ix];
for (iy=0;iy<order;iy++) for (int iy = 0; iy < order; iy++) {
{ int yindex = (y0index + iy) % ngrid[1];
yindex = (y0index + iy) % pme->ngrid[1];
/* bspline wrt y */ /* bspline wrt y */
ty = thetay[iy]; double ty = thetay[iy];
for (iz=0;iz<order;iz++) for (int iz = 0; iz < order; iz++) {
{
/* Can be optimized, but we keep it simple here */ /* Can be optimized, but we keep it simple here */
zindex = (z0index + iz) % pme->ngrid[2]; int zindex = (z0index + iz) % ngrid[2];
/* bspline wrt z */ /* bspline wrt z */
tz = thetaz[iz]; double tz = thetaz[iz];
index = xindex*pme->ngrid[1]*pme->ngrid[2] + yindex*pme->ngrid[2] + zindex; int index = xindex*ngrid[1]*ngrid[2] + yindex*ngrid[2] + zindex;
/* Get the fft+convoluted+ifft:d data from the grid, which must be real by definition */ /* Get the fft+convoluted+ifft:d data from the grid, which must be real by definition */
/* Checking that the imaginary part is indeed zero might be a good check :-) */ /* Checking that the imaginary part is indeed zero might be a good check :-) */
gridvalue = pme->grid[index].real(); double gridvalue = grid[index].real();
/* The d component of the force is calculated by taking the derived bspline in dimension d, normal bsplines in the other two */ /* The d component of the force is calculated by taking the derived bspline in dimension d, normal bsplines in the other two */
dq += tx*ty*tz*gridvalue; dq += tx*ty*tz*gridvalue;
...@@ -794,222 +531,146 @@ pme_grid_interpolate_charge_derivatives(pme_t pme, ...@@ -794,222 +531,146 @@ pme_grid_interpolate_charge_derivatives(pme_t pme,
} }
} }
ReferencePME::ReferencePME(double ewaldcoeff, int natoms, const int ngrid[3], int pme_order, double epsilon_r) :
order(pme_order), epsilon_r(epsilon_r), ewaldcoeff(ewaldcoeff), natoms(natoms) {
/* EXPORTED ROUTINES */ for (int d = 0; d < 3; d++) {
this->ngrid[d] = ngrid[d];
int bsplines_theta[d].resize(pme_order*natoms);
pme_init(pme_t * ppme, bsplines_dtheta[d].resize(pme_order*natoms);
double ewaldcoeff,
int natoms,
const int ngrid[3],
int pme_order,
double epsilon_r)
{
pme_t pme;
int d;
pme = (pme_t) malloc(sizeof(struct pme));
pme->order = pme_order;
pme->epsilon_r = epsilon_r;
pme->ewaldcoeff = ewaldcoeff;
pme->natoms = natoms;
for (d=0;d<3;d++)
{
pme->ngrid[d] = ngrid[d];
pme->bsplines_theta[d] = (double *)malloc(sizeof(double)*pme_order*natoms);
pme->bsplines_dtheta[d] = (double *)malloc(sizeof(double)*pme_order*natoms);
} }
pme->particlefraction = (rvec *)malloc(sizeof(rvec)*natoms); particlefraction.resize(natoms);
pme->particleindex = (ivec *)malloc(sizeof(ivec)*natoms); particleindex.resize(natoms);
/* Allocate charge grid storage */ /* Allocate charge grid storage */
pme->grid = (complex<double> *)malloc(sizeof(complex<double>)*ngrid[0]*ngrid[1]*ngrid[2]); grid.resize(ngrid[0]*ngrid[1]*ngrid[2]);
/* Setup bspline moduli (see Essman paper) */ /* Setup bspline moduli (see Essman paper) */
pme_calculate_bsplines_moduli(pme); calculate_bsplines_moduli();
*ppme = pme;
return 0;
} }
void ReferencePME::exec(const vector<Vec3>& atomCoordinates, vector<Vec3>& forces, const vector<double>& charges,
const Vec3 periodicBoxVectors[3], double& energy) {
int pme_exec(pme_t pme,
const vector<Vec3>& atomCoordinates,
vector<Vec3>& forces,
const vector<double>& charges,
const Vec3 periodicBoxVectors[3],
double* energy)
{
/* Routine is called with coordinates in x, a box, and charges in q */ /* Routine is called with coordinates in x, a box, and charges in q */
Vec3 recipBoxVectors[3]; Vec3 recipBoxVectors[3];
ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors); ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors);
/* Before we can do the actual interpolation, we need to recalculate and update /* Before we can do the actual interpolation, we need to recalculate and update
* the indices for each particle in the charge grid (initialized in pme_init()), * the indices for each particle in the charge grid (initialized in constructor),
* and what its fractional offset in this grid cell is. * and what its fractional offset in this grid cell is.
*/ */
/* Update charge grid indices and fractional offsets for each atom. /* Update charge grid indices and fractional offsets for each atom.
* The indices/fractions are stored internally in the pme datatype * The indices/fractions are stored internally in the pme datatype
*/ */
pme_update_grid_index_and_fraction(pme,atomCoordinates,periodicBoxVectors,recipBoxVectors); update_grid_index_and_fraction(atomCoordinates, recipBoxVectors);
/* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */ /* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */
pme_update_bsplines(pme); update_bsplines();
/* Spread the charges on grid (using newly calculated bsplines in the pme structure) */ /* Spread the charges on grid (using newly calculated bsplines in the pme structure) */
pme_grid_spread_charge(pme, charges); grid_spread_charge(charges);
/* do 3d-fft */ /* do 3d-fft */
vector<size_t> shape = {(size_t) pme->ngrid[0], (size_t) pme->ngrid[1], (size_t) pme->ngrid[2]}; vector<size_t> shape = {(size_t) ngrid[0], (size_t) ngrid[1], (size_t) ngrid[2]};
vector<size_t> axes = {0, 1, 2}; vector<size_t> axes = {0, 1, 2};
vector<ptrdiff_t> stride = {(ptrdiff_t) (pme->ngrid[1]*pme->ngrid[2]*sizeof(complex<double>)), vector<ptrdiff_t> stride = {(ptrdiff_t) (ngrid[1]*ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) (pme->ngrid[2]*sizeof(complex<double>)), (ptrdiff_t) (ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) sizeof(complex<double>)}; (ptrdiff_t) sizeof(complex<double>)};
pocketfft::c2c(shape, stride, stride, axes, true, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, true, grid.data(), grid.data(), 1.0, 0);
/* solve in k-space */ /* solve in k-space */
pme_reciprocal_convolution(pme,periodicBoxVectors,recipBoxVectors,energy); pme_reciprocal_convolution(periodicBoxVectors, recipBoxVectors, energy);
/* do 3d-invfft */ /* do 3d-invfft */
pocketfft::c2c(shape, stride, stride, axes, false, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, false, grid.data(), grid.data(), 1.0, 0);
/* Get the particle forces from the grid and bsplines in the pme structure */ /* Get the particle forces from the grid and bsplines in the pme structure */
pme_grid_interpolate_force(pme,recipBoxVectors,charges,forces); grid_interpolate_force(recipBoxVectors, charges, forces);
return 0;
} }
void ReferencePME::exec_charge_derivatives(const vector<Vec3>& atomCoordinates, vector<double>& chargeDerivatives,
int pme_exec_charge_derivatives(pme_t pme, const vector<int>& chargeIndices, const vector<double>& charges, const Vec3 periodicBoxVectors[3]) {
const vector<Vec3>& atomCoordinates,
vector<double>& chargeDerivatives,
const vector<int>& chargeIndices,
const vector<double>& charges,
const Vec3 periodicBoxVectors[3])
{
/* Routine is called with coordinates in x, a box, and charges in q */ /* Routine is called with coordinates in x, a box, and charges in q */
Vec3 recipBoxVectors[3]; Vec3 recipBoxVectors[3];
ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors); ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors);
/* Before we can do the actual interpolation, we need to recalculate and update /* Before we can do the actual interpolation, we need to recalculate and update
* the indices for each particle in the charge grid (initialized in pme_init()), * the indices for each particle in the charge grid (initialized in constructor),
* and what its fractional offset in this grid cell is. * and what its fractional offset in this grid cell is.
*/ */
/* Update charge grid indices and fractional offsets for each atom. /* Update charge grid indices and fractional offsets for each atom.
* The indices/fractions are stored internally in the pme datatype * The indices/fractions are stored internally in the pme datatype
*/ */
pme_update_grid_index_and_fraction(pme,atomCoordinates,periodicBoxVectors,recipBoxVectors); update_grid_index_and_fraction(atomCoordinates, recipBoxVectors);
/* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */ /* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */
pme_update_bsplines(pme); update_bsplines();
/* Spread the charges on grid (using newly calculated bsplines in the pme structure) */ /* Spread the charges on grid (using newly calculated bsplines in the pme structure) */
pme_grid_spread_charge(pme, charges); grid_spread_charge(charges);
/* do 3d-fft */ /* do 3d-fft */
vector<size_t> shape = {(size_t) pme->ngrid[0], (size_t) pme->ngrid[1], (size_t) pme->ngrid[2]}; vector<size_t> shape = {(size_t) ngrid[0], (size_t) ngrid[1], (size_t) ngrid[2]};
vector<size_t> axes = {0, 1, 2}; vector<size_t> axes = {0, 1, 2};
vector<ptrdiff_t> stride = {(ptrdiff_t) (pme->ngrid[1]*pme->ngrid[2]*sizeof(complex<double>)), vector<ptrdiff_t> stride = {(ptrdiff_t) (ngrid[1]*ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) (pme->ngrid[2]*sizeof(complex<double>)), (ptrdiff_t) (ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) sizeof(complex<double>)}; (ptrdiff_t) sizeof(complex<double>)};
pocketfft::c2c(shape, stride, stride, axes, true, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, true, grid.data(), grid.data(), 1.0, 0);
/* solve in k-space */ /* solve in k-space */
double energy; double energy;
pme_reciprocal_convolution(pme,periodicBoxVectors,recipBoxVectors,&energy); pme_reciprocal_convolution(periodicBoxVectors, recipBoxVectors, energy);
/* do 3d-invfft */ /* do 3d-invfft */
pocketfft::c2c(shape, stride, stride, axes, false, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, false, grid.data(), grid.data(), 1.0, 0);
/* Get the charge derivatives from the grid and bsplines in the pme structure */ /* Get the charge derivatives from the grid and bsplines in the pme structure */
pme_grid_interpolate_charge_derivatives(pme,recipBoxVectors,charges,chargeDerivatives,chargeIndices); grid_interpolate_charge_derivatives(recipBoxVectors, charges, chargeDerivatives, chargeIndices);
return 0;
} }
void ReferencePME::exec_dpme(const vector<Vec3>& atomCoordinates, vector<Vec3>& forces, const vector<double>& c6s,
int pme_exec_dpme(pme_t pme, const Vec3 periodicBoxVectors[3], double& energy) {
const vector<Vec3>& atomCoordinates,
vector<Vec3>& forces,
const vector<double>& c6s,
const Vec3 periodicBoxVectors[3],
double* energy)
{
/* Routine is called with coordinates in x, a box, and charges in q */ /* Routine is called with coordinates in x, a box, and charges in q */
Vec3 recipBoxVectors[3]; Vec3 recipBoxVectors[3];
ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors); ReferenceForce::invertBoxVectors(periodicBoxVectors, recipBoxVectors);
/* Before we can do the actual interpolation, we need to recalculate and update /* Before we can do the actual interpolation, we need to recalculate and update
* the indices for each particle in the charge grid (initialized in pme_init()), * the indices for each particle in the charge grid (initialized in constructor),
* and what its fractional offset in this grid cell is. * and what its fractional offset in this grid cell is.
*/ */
/* Update charge grid indices and fractional offsets for each atom. /* Update charge grid indices and fractional offsets for each atom.
* The indices/fractions are stored internally in the pme datatype * The indices/fractions are stored internally in the pme datatype
*/ */
pme_update_grid_index_and_fraction(pme,atomCoordinates,periodicBoxVectors,recipBoxVectors); update_grid_index_and_fraction(atomCoordinates, recipBoxVectors);
/* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */ /* Calculate bsplines (and their differentials) from current fractional coordinates, store in pme structure */
pme_update_bsplines(pme); update_bsplines();
/* Spread the charges on grid (using newly calculated bsplines in the pme structure) */ /* Spread the charges on grid (using newly calculated bsplines in the pme structure) */
pme_grid_spread_charge(pme, c6s); grid_spread_charge(c6s);
/* do 3d-fft */ /* do 3d-fft */
vector<size_t> shape = {(size_t) pme->ngrid[0], (size_t) pme->ngrid[1], (size_t) pme->ngrid[2]}; vector<size_t> shape = {(size_t) ngrid[0], (size_t) ngrid[1], (size_t) ngrid[2]};
vector<size_t> axes = {0, 1, 2}; vector<size_t> axes = {0, 1, 2};
vector<ptrdiff_t> stride = {(ptrdiff_t) (pme->ngrid[1]*pme->ngrid[2]*sizeof(complex<double>)), vector<ptrdiff_t> stride = {(ptrdiff_t) (ngrid[1]*ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) (pme->ngrid[2]*sizeof(complex<double>)), (ptrdiff_t) (ngrid[2]*sizeof(complex<double>)),
(ptrdiff_t) sizeof(complex<double>)}; (ptrdiff_t) sizeof(complex<double>)};
pocketfft::c2c(shape, stride, stride, axes, true, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, true, grid.data(), grid.data(), 1.0, 0);
/* solve in k-space */ /* solve in k-space */
dpme_reciprocal_convolution(pme,periodicBoxVectors,recipBoxVectors,energy); dpme_reciprocal_convolution(periodicBoxVectors, recipBoxVectors, energy);
/* do 3d-invfft */ /* do 3d-invfft */
pocketfft::c2c(shape, stride, stride, axes, false, pme->grid, pme->grid, 1.0, 0); pocketfft::c2c(shape, stride, stride, axes, false, grid.data(), grid.data(), 1.0, 0);
/* Get the particle forces from the grid and bsplines in the pme structure */ /* Get the particle forces from the grid and bsplines in the pme structure */
pme_grid_interpolate_force(pme,recipBoxVectors,c6s,forces); grid_interpolate_force(recipBoxVectors, c6s, forces);
return 0;
}
int
pme_destroy(pme_t pme)
{
int d;
free(pme->grid);
for (d=0;d<3;d++)
{
free(pme->bsplines_moduli[d]);
free(pme->bsplines_theta[d]);
free(pme->bsplines_dtheta[d]);
}
free(pme->particlefraction);
free(pme->particleindex);
/* destroy structure itself */
free(pme);
return 0;
} }
} // namespace OpenMM } // namespace OpenMM
...@@ -59,14 +59,13 @@ void testReferencePmeDerivatives() { ...@@ -59,14 +59,13 @@ void testReferencePmeDerivatives() {
double ewaldAlpha = 1.752; double ewaldAlpha = 1.752;
int gridSize[3] = {54, 49, 43}; int gridSize[3] = {54, 49, 43};
pme_t pme; ReferencePME pme(ewaldAlpha, numParticles, gridSize, 5, 1);
pme_init(&pme, ewaldAlpha, numParticles, gridSize, 5, 1);
double dummyEnergy=0; double dummyEnergy=0;
vector<Vec3> dummyForces(numParticles); vector<Vec3> dummyForces(numParticles);
vector<Vec3> testForces(numParticles); vector<Vec3> testForces(numParticles);
pme_exec(pme, positions, testForces, charges, boxVectors, &dummyEnergy); pme.exec(positions, testForces, charges, boxVectors, dummyEnergy);
for (int i = 0; i < numParticles; i++) { for (int i = 0; i < numParticles; i++) {
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
...@@ -74,11 +73,11 @@ void testReferencePmeDerivatives() { ...@@ -74,11 +73,11 @@ void testReferencePmeDerivatives() {
double energyLess = 0.0; double energyLess = 0.0;
positions[i][j] = referencePosition - DELTA; positions[i][j] = referencePosition - DELTA;
pme_exec(pme, positions, dummyForces, charges, boxVectors, &energyLess); pme.exec(positions, dummyForces, charges, boxVectors, energyLess);
double energyMore = 0.0; double energyMore = 0.0;
positions[i][j] = referencePosition + DELTA; positions[i][j] = referencePosition + DELTA;
pme_exec(pme, positions, dummyForces, charges, boxVectors, &energyMore); pme.exec(positions, dummyForces, charges, boxVectors, energyMore);
positions[i][j] = referencePosition; positions[i][j] = referencePosition;
...@@ -87,25 +86,23 @@ void testReferencePmeDerivatives() { ...@@ -87,25 +86,23 @@ void testReferencePmeDerivatives() {
} }
vector<double> testDerivatives(numParticles); vector<double> testDerivatives(numParticles);
pme_exec_charge_derivatives(pme, positions, testDerivatives, indices, charges, boxVectors); pme.exec_charge_derivatives(positions, testDerivatives, indices, charges, boxVectors);
for (int i = 0; i < numParticles; i++) { for (int i = 0; i < numParticles; i++) {
double referenceCharge = charges[i]; double referenceCharge = charges[i];
double energyLess = 0.0; double energyLess = 0.0;
charges[i] = referenceCharge - DELTA; charges[i] = referenceCharge - DELTA;
pme_exec(pme, positions, dummyForces, charges, boxVectors, &energyLess); pme.exec(positions, dummyForces, charges, boxVectors, energyLess);
double energyMore = 0.0; double energyMore = 0.0;
charges[i] = referenceCharge + DELTA; charges[i] = referenceCharge + DELTA;
pme_exec(pme, positions, dummyForces, charges, boxVectors, &energyMore); pme.exec(positions, dummyForces, charges, boxVectors, energyMore);
charges[i] = referenceCharge; charges[i] = referenceCharge;
ASSERT_EQUAL_TOL((energyMore - energyLess) / (2 * DELTA), testDerivatives[i], EPSILON); ASSERT_EQUAL_TOL((energyMore - energyLess) / (2 * DELTA), testDerivatives[i], EPSILON);
} }
pme_destroy(pme);
} }
void runPlatformTests() { void runPlatformTests() {
......
...@@ -2873,8 +2873,7 @@ double AmoebaReferencePmeHippoNonbondedForce::calculateDispersionPairIxn(const M ...@@ -2873,8 +2873,7 @@ double AmoebaReferencePmeHippoNonbondedForce::calculateDispersionPairIxn(const M
} }
double AmoebaReferencePmeHippoNonbondedForce::computeReciprocalSpaceDispersionForceAndEnergy(const vector<MultipoleParticleData>& particleData, vector<Vec3>& forces) const { double AmoebaReferencePmeHippoNonbondedForce::computeReciprocalSpaceDispersionForceAndEnergy(const vector<MultipoleParticleData>& particleData, vector<Vec3>& forces) const {
pme_t pmedata; ReferencePME pme(_dalphaEwald, _numParticles, _dpmeGridDimensions, 5, 1);
pme_init(&pmedata, _dalphaEwald, _numParticles, _dpmeGridDimensions, 5, 1);
vector<double> charges(_numParticles); vector<double> charges(_numParticles);
vector<Vec3> dpmeforces(_numParticles, Vec3()), coords; vector<Vec3> dpmeforces(_numParticles, Vec3()), coords;
for (int i = 0; i < _numParticles; i++) { for (int i = 0; i < _numParticles; i++) {
...@@ -2882,8 +2881,7 @@ double AmoebaReferencePmeHippoNonbondedForce::computeReciprocalSpaceDispersionFo ...@@ -2882,8 +2881,7 @@ double AmoebaReferencePmeHippoNonbondedForce::computeReciprocalSpaceDispersionFo
coords.push_back(particleData[i].position); coords.push_back(particleData[i].position);
} }
double recipDispersionEnergy; double recipDispersionEnergy;
pme_exec_dpme(pmedata, coords, dpmeforces, charges, _periodicBoxVectors, &recipDispersionEnergy); pme.exec_dpme(coords, dpmeforces, charges, _periodicBoxVectors, recipDispersionEnergy);
pme_destroy(pmedata);
for (int i = 0; i < _numParticles; i++) for (int i = 0; i < _numParticles; i++)
forces[i] += dpmeforces[i]; forces[i] += dpmeforces[i];
return recipDispersionEnergy; return recipDispersionEnergy;
......
...@@ -650,12 +650,10 @@ void testPME(bool triclinic, bool nonNeutral) { ...@@ -650,12 +650,10 @@ void testPME(bool triclinic, bool nonNeutral) {
// Get charge derivatives from the reference PME implementation. // Get charge derivatives from the reference PME implementation.
pme_t referencePme;
int gridSize[3] = {gridx, gridy, gridz}; int gridSize[3] = {gridx, gridy, gridz};
pme_init(&referencePme, alpha, numParticles, gridSize, 5, 1); ReferencePME referencePme(alpha, numParticles, gridSize, 5, 1);
vector<double> testDerivatives(numParticles); vector<double> testDerivatives(numParticles);
pme_exec_charge_derivatives(referencePme, positions, testDerivatives, testIndices, testCharges, boxVectors); referencePme.exec_charge_derivatives(positions, testDerivatives, testIndices, testCharges, boxVectors);
pme_destroy(referencePme);
// See if they match. // See if they match.
for (int i = 0; i < numParticles; i++) { for (int i = 0; i < numParticles; i++) {
......
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