View Javadoc
1   package ffx.numerics.multipole;
2   
3   import java.util.Arrays;
4   
5   import static java.lang.Math.abs;
6   import static org.apache.commons.math3.util.FastMath.exp;
7   import static org.apache.commons.math3.util.FastMath.pow;
8   
9   /**
10   * The AmoebaPlusDampTensorGlobal class computes derivatives of damping via recursion to
11   * order <= 2 for Cartesian multipoles defined in AMOEBA+. These are the nuclear charge
12   * interactions with permanent multipoles. The zeroth order damp term is 1-exp(-alpha*r).
13   *
14   * @author Matthew J. Speranza
15   * @see <a href="http://doi.org/10.1142/9789812830364_0002" target="_blank"> Matt Challacombe, Eric
16   * Schwegler and Jan Almlof, Modern developments in Hartree-Fock theory: Fast methods for
17   * computing the Coulomb matrix. Computational Chemistry: Review of Current Trends. pp. 53-107,
18   * Ed. J. Leczszynski, World Scientifc, 1996. </a>
19   * @see <a href="https://doi.org/10.1039/C6CP06017J" target="_blank"> Rackers, Wang, Liu,
20   * Piquemal, Ren and Ponder, An optimized charge penetration model for use with the AMOEBA
21   * force field. Phys. Chem. Chem. Phys., 2017,19, 276-291. </a>
22   * @since 1.0
23   */
24  public class AmoebaPlusDampTensorGlobal extends CoulombTensorGlobal {
25  
26    private double beta = 0.0;
27    private double alpha;
28    private double alpha2;
29    private static final double oneThird = 1.0 / 3.0;
30    private double[] ewaldSource;
31  
32    /**
33     * Constructor for CoulombTensorGlobal.
34     *
35     * @param order  The order of the tensor.
36     * @param alpha1 The first damping parameter.
37     * @param alpha2 The second damping parameter.
38     */
39    public AmoebaPlusDampTensorGlobal(int order, double alpha1, double alpha2) {
40      super(order);
41      this.alpha = alpha1;
42      this.alpha2 = alpha2;
43      this.operator = abs(alpha - alpha2) < 1e-5 ?
44          Operator.AMOEBA_PLUS_SYM_DAMP_FIELD : Operator.AMOEBA_PLUS_DAMP_FIELD;
45      assert (order <= 3); // Nuclear charge is a point charge
46    }
47  
48    /**
49     * Constructor for CoulombTensorGlobal.
50     *
51     * @param order  The order of the tensor.
52     * @param alpha1 The first damping parameter.
53     * @param alpha2 The second damping parameter.
54     * @param ewaldA The Ewald damping parameter.
55     */
56    public AmoebaPlusDampTensorGlobal(int order, double alpha1, double alpha2, double ewaldA) {
57      this(order, alpha1, alpha2);
58      this.beta = ewaldA;
59    }
60  
61  
62    /**
63     * Generate source terms for the Challacombe et al. recursion.
64     *
65     * @param T000 Location to store the source terms.
66     */
67    @Override
68    protected void source(double[] T000) {
69      // Compute the normal Coulomb auxiliary term.
70      super.source(T000);
71      double[] copy = Arrays.copyOf(T000, o1);
72      // Add the damping term: edamp = 1-exp(-alpha*r).
73      dampSource(alpha, R, T000);
74      if (beta > 1e-3) {
75        this.ewaldSource = new double[this.order + 1];
76        EwaldTensorGlobal.fillEwaldSource(this.order, beta,
77            EwaldTensorGlobal.initEwaldSource(this.order, beta, new double[o1]),
78            this.R, ewaldSource);
79        // T000 = Ewald - Coulomb + Core
80        for (int i = 0; i < ewaldSource.length; i++) {
81          T000[i] += ewaldSource[i] - copy[i];
82        }
83      }
84    }
85  
86    /**
87     * Generate source terms for the Challacombe et al. recursion.
88     *
89     * @param alpha adjustable damping parameter
90     * @param R     The separation distance.
91     * @param T000  Location to store the source terms.
92     */
93    protected static void dampSource(double alpha, double R, double[] T000) {
94      // Add the damping terms: edamp = 1-exp(-alpha*r).
95      double alphaR = alpha * R;
96      double alphaR2 = alphaR * alphaR;
97      double expAR = exp(-alphaR);
98  
99      T000[0] *= 1 - expAR;
100     T000[1] *= 1 - (1 + alphaR) * expAR;
101     T000[2] *= 1 - (1.0 + alphaR + oneThird * alphaR2) * expAR;
102     T000[3] *= 1 - (1.0 + alphaR + .4 * alphaR2 + alphaR2 * alphaR / 15) * expAR;
103   }
104 
105 
106   /**
107    * Terms 1, 2, 3 in Eq. 5 of AMOEBA+ paper. Uses a swap of alpha -> alpha2
108    * that takes place in the first call to the source method to generate a new
109    * tensor with the second alpha.
110    *
111    * @param mI The multipole moment at site I.
112    * @param mK The multipole moment at site K.
113    * @return energy.
114    */
115   public double coreInteraction(PolarizableMultipole mI, PolarizableMultipole mK) {
116     // Coulomb energy of core charges (No damping)
117     double energy = beta >= 1e-3 ? mK.Z * mI.Z * ewaldSource[0] : mK.Z * mI.Z / R;
118 
119     // Cores contract with multipole moments
120     multipoleIPotentialAtK(mI, 0);
121     energy += mK.Z * E000;
122     if (this.operator != Operator.AMOEBA_PLUS_SYM_DAMP_FIELD) {
123       // Generate tensor with alpha2 for term 2 -> Zi * T(damp)ij * Mj
124       double temp = this.alpha;
125       this.alpha = this.alpha2;
126       this.alpha2 = temp;
127       this.generateTensor();
128       temp = this.alpha;
129       this.alpha = this.alpha2;
130       this.alpha2 = temp;
131     }
132     multipoleKPotentialAtI(mK, 0);
133     energy += mI.Z * E000;
134 
135     return energy;
136   }
137 
138   /**
139    * Compute the core interaction and gradient between two sites.
140    *
141    * @param mI The multipole moment at site I.
142    * @param mK The multipole moment at site K.
143    * @param Gi The gradient at site I.
144    * @param Gk The gradient at site K.
145    * @return energy.
146    */
147   public double coreInteractionAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
148                                            double[] Gi, double[] Gk) {
149     // Coulomb energy of core charges (No damping -> cant use tensor terms)
150     double energy = this.beta >= 1e-3 ? mK.Z * mI.Z * this.ewaldSource[0] : mK.Z * mI.Z / R;
151     double tensorElement = this.beta >= 1e-3 ? this.ewaldSource[1] : -pow(R, -3);
152     Gk[0] = mK.Z * mI.Z * x * tensorElement;
153     Gk[1] = mK.Z * mI.Z * y * tensorElement;
154     Gk[2] = mK.Z * mI.Z * z * tensorElement;
155 
156     // Cores contract with multipole moments
157     multipoleIPotentialAtK(mI, 1);
158     energy += mK.Z * E000;
159     Gk[0] += mK.Z * E100;
160     Gk[1] += mK.Z * E010;
161     Gk[2] += mK.Z * E001;
162     if (this.operator != Operator.AMOEBA_PLUS_SYM_DAMP_FIELD) {
163       // Generate tensor with alpha2 for term 2 -> Zi * T(damp)ij * Mj
164       // R = |Rj - Ri| = Rji --> T(damp)ij != T(damp)ji after first order?
165       double temp = this.alpha;
166       this.alpha = this.alpha2;
167       this.alpha2 = temp;
168       this.generateTensor();
169       temp = this.alpha;
170       this.alpha = this.alpha2;
171       this.alpha2 = temp;
172     }
173     multipoleKPotentialAtI(mK, 1);
174     energy += mI.Z * E000;
175     // mI.Z * EXXX = derivative Gi[X], so flip sign
176     Gk[0] -= mI.Z * E100;
177     Gk[1] -= mI.Z * E010;
178     Gk[2] -= mI.Z * E001;
179 
180     Gi[0] = -Gk[0];
181     Gi[1] = -Gk[1];
182     Gi[2] = -Gk[2];
183 
184     return energy;
185   }
186 
187 }