View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.parameters;
39  
40  import ffx.utilities.FFXProperty;
41  import org.w3c.dom.Document;
42  import org.w3c.dom.Element;
43  
44  import java.io.BufferedReader;
45  import java.io.IOException;
46  import java.util.Arrays;
47  import java.util.Comparator;
48  import java.util.HashMap;
49  import java.util.Map;
50  import java.util.logging.Level;
51  import java.util.logging.Logger;
52  
53  import static ffx.potential.parameters.ForceField.ForceFieldType.TORTORS;
54  import static ffx.utilities.Constants.KCAL_TO_KJ;
55  import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
56  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
57  import static java.lang.Double.parseDouble;
58  import static java.lang.Integer.parseInt;
59  import static java.lang.String.format;
60  import static java.lang.System.arraycopy;
61  import static java.util.Arrays.sort;
62  import static org.apache.commons.math3.util.FastMath.abs;
63  
64  /**
65   * The TorsionTorsionType class defines a Torsion-Torsion spline.
66   *
67   * @author Michael J. Schnieders
68   * @since 1.0
69   */
70  @FFXProperty(name = "tortors", clazz = String[].class, propertyGroup = PotentialFunctionParameter, description = """
71      [7 integers, then multiple lines of 2 integers and 1 real]
72      Provides the values for a single torsion-torsion parameter.
73      The first five integer modifiers give the atom class numbers for the atoms involved in the two adjacent torsional angles to be defined.
74      The last two integer modifiers contain the number of data grid points that lie along each axis of the torsion-torsion map.
75      For example, this value will be 13 for a 30 degree torsional angle spacing, i.e., 360/30 = 12, but 13
76      values are required since data values for -180 and +180 degrees must both be supplied.
77      The subsequent lines contain the torsion-torsion map data as the integer values in degrees of each
78      torsional angle and the target energy value in kcal/mole.
79      """)
80  public final class TorsionTorsionType extends BaseType implements Comparator<String> {
81  
82    /**
83     * Default units to convert Torsion-Torsion energy to kcal/mole.
84     */
85    public static final double DEFAULT_TORTOR_UNIT = 1.0;
86    /**
87     * Convert Torsion-Torsion energy to kcal/mole.
88     */
89    @FFXProperty(name = "tortorunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
90        Sets the scale factor needed to convert the energy value computed by the torsion-torsion potential into units of kcal/mole.
91        The correct value is force field dependent and typically provided in the header of the master force field parameter file.
92        """)
93    public double torTorUnit = DEFAULT_TORTOR_UNIT;
94  
95    private static final Logger logger = Logger.getLogger(TorsionTorsionType.class.getName());
96    /**
97     * Atom classes that form this Torsion-Torsion type.
98     */
99    public final int[] atomClasses;
100   /**
101    * Energy values.
102    */
103   public final double[] energy;
104   /**
105    * Number of points along x.
106    */
107   public final int nx;
108   /**
109    * Number of point along y.
110    */
111   public final int ny;
112   /**
113    * Torsion values along x.
114    */
115   public final double[] tx;
116   /**
117    * Torsion values along y.
118    */
119   public final double[] ty;
120   /**
121    * First derivative along x.
122    */
123   public final double[] dx;
124   /**
125    * First derivative along y.
126    */
127   public final double[] dy;
128   /**
129    * Second derivatives.
130    */
131   public final double[] dxy;
132   /**
133    * Grid points.
134    */
135   private final int[] gridPoints;
136 
137   /**
138    * Constructor for TorsionTorsionType.
139    *
140    * @param atomClasses an array of int.
141    * @param gridPoints  an array of int.
142    * @param torsion1    an array of double.
143    * @param torsion2    an array of double.
144    * @param energy      an array of double.
145    */
146   public TorsionTorsionType(int[] atomClasses, int[] gridPoints, double[] torsion1,
147                             double[] torsion2, double[] energy) {
148     super(TORTORS, sortKey(atomClasses));
149     this.atomClasses = atomClasses;
150     nx = gridPoints[0];
151     ny = gridPoints[1];
152     if (nx != ny) {
153       logger.severe("Untested TORTOR parameters: nx != ny: " + nx + ", " + ny);
154     }
155 
156     this.energy = energy;
157     this.gridPoints = gridPoints;
158     tx = new double[nx];
159     ty = new double[ny];
160     dx = new double[nx * ny];
161     dy = new double[nx * ny];
162     dxy = new double[nx * ny];
163     sort(torsion1);
164     sort(torsion2);
165     tx[0] = torsion1[0];
166     ty[0] = torsion2[0];
167     int j1 = 1;
168     int j2 = 1;
169     for (int i = 1; i < nx; i++) {
170       while (torsion1[j1] == tx[i - 1]) {
171         j1++;
172       }
173       while (torsion2[j2] == ty[i - 1]) {
174         j2++;
175       }
176       tx[i] = torsion1[j1];
177       ty[i] = torsion2[j2];
178     }
179 
180     // Check for cyclic energy.
181     boolean isCyclic = true;
182     double eps = 0.0001;
183     if (abs(tx[0] - tx[nx - 1]) - 360.0 > eps) {
184       isCyclic = false;
185       if (logger.isLoggable(Level.FINEST)) {
186         logger.finest(" tortor is aperiodic: " + tx[0] + ", " + tx[nx - 1]);
187       }
188     }
189     if (isCyclic) {
190       for (int i = 0; i < ny; i++) {
191         int k = i * nx;
192         if (abs(energy[k] - energy[k + nx - 1]) > eps) {
193           isCyclic = false;
194           if (logger.isLoggable(Level.FINEST)) {
195             logger.finest(" tortor is apreriodic: " + k + ", " + (k + nx - 1) + ": " + abs(
196                 energy[k] - energy[k + nx - 1]));
197           }
198           break;
199         }
200       }
201     }
202     if (isCyclic) {
203       int k = (ny - 1) * nx;
204       for (int i = 0; i < nx; i++) {
205         if (abs(energy[i] - energy[i + k]) > eps) {
206           if (logger.isLoggable(Level.FINEST)) {
207             logger.fine(
208                 " tortor is aperiodic: " + i + ", " + i + k + ": " + abs(energy[i] - energy[i + k]));
209           }
210           isCyclic = false;
211           break;
212         }
213       }
214     }
215 
216     boolean cyclic = isCyclic;
217     double[] tmp1 = new double[nx];
218     double[] tmp2 = new double[nx];
219     double[] tmp3 = new double[nx];
220     double[] tmp4 = new double[nx];
221     double[] tmp5 = new double[nx];
222     double[] tmp6 = new double[nx];
223     double[] tmp7 = new double[nx];
224     double[] bs = new double[nx];
225     double[] cs = new double[nx];
226     double[] ds = new double[nx];
227 
228     // Spline fit the derivatives about the first torsion.
229     arraycopy(tx, 0, tmp1, 0, nx);
230 
231     int m = 0;
232     for (int j = 0; j < ny; j++) {
233       arraycopy(energy, m, tmp2, 0, nx);
234       if (cyclic) {
235         cspline(nx - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
236       } else {
237         nspline(nx - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
238       }
239       arraycopy(bs, 0, dx, m, nx);
240       m = m + nx;
241     }
242 
243     // Spline fit the derivatives about the second torsion.
244     arraycopy(ty, 0, tmp1, 0, ny);
245 
246     m = 0;
247     for (int j = 0; j < nx; j++) {
248       for (int k = 0; k < ny; k++) {
249         tmp2[k] = energy[m + k * nx];
250       }
251       if (cyclic) {
252         cspline(ny - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
253       } else {
254         nspline(ny - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
255       }
256       for (int k = 0; k < ny; k++) {
257         dy[m + k * nx] = bs[k];
258       }
259       m = m + 1;
260     }
261 
262     // Spline fit the cross derivatives about both torsions.
263     m = 0;
264     for (int j = 0; j < nx; j++) {
265       for (int k = 0; k < ny; k++) {
266         tmp2[k] = dx[m + k * nx];
267       }
268       if (cyclic) {
269         cspline(ny - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
270       } else {
271         nspline(ny - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
272       }
273       for (int k = 0; k < ny; k++) {
274         dxy[m + k * nx] = bs[k];
275       }
276       m = m + 1;
277     }
278   }
279 
280   /**
281    * average.
282    *
283    * @param torsionTorsionType1 a {@link ffx.potential.parameters.TorsionTorsionType} object.
284    * @param torsionTorsionType2 a {@link ffx.potential.parameters.TorsionTorsionType} object.
285    * @param atomClasses         an array of {@link int} objects.
286    * @return a {@link ffx.potential.parameters.TorsionTorsionType} object.
287    */
288   public static TorsionTorsionType average(TorsionTorsionType torsionTorsionType1,
289                                            TorsionTorsionType torsionTorsionType2, int[] atomClasses) {
290     if (torsionTorsionType1 == null || torsionTorsionType2 == null || atomClasses == null) {
291       return null;
292     }
293     return null;
294   }
295 
296   /**
297    * Construct a TorsionTorsionType from multiple input lines.
298    *
299    * @param input  The overall input String.
300    * @param tokens The input String tokenized.
301    * @param br     a BufferedReader instance.
302    * @return a TorsionTorsionType instance.
303    */
304   public static TorsionTorsionType parse(String input, String[] tokens, BufferedReader br) {
305     if (tokens.length < 8) {
306       logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
307     } else {
308       try {
309         int[] atomClasses = new int[5];
310         for (int i = 0; i < 5; i++) {
311           atomClasses[i] = parseInt(tokens[i + 1]);
312         }
313         int[] gridPoints = new int[2];
314         gridPoints[0] = parseInt(tokens[6]);
315         gridPoints[1] = parseInt(tokens[7]);
316         int points = gridPoints[0] * gridPoints[1];
317         double[] torsion1 = new double[points];
318         double[] torsion2 = new double[points];
319         double[] energy = new double[points];
320         for (int i = 0; i < points; i++) {
321           input = br.readLine();
322           tokens = input.trim().split(" +");
323           if (tokens.length != 3) {
324             logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
325             return null;
326           }
327           torsion1[i] = parseDouble(tokens[0]);
328           torsion2[i] = parseDouble(tokens[1]);
329           energy[i] = parseDouble(tokens[2]);
330         }
331         return new TorsionTorsionType(atomClasses, gridPoints, torsion1, torsion2, energy);
332       } catch (NumberFormatException | IOException e) {
333         String message = "Exception parsing TORTORS type:\n" + input + "\n";
334         logger.log(Level.SEVERE, message, e);
335       }
336     }
337     return null;
338   }
339 
340   /**
341    * Construct a TorsionTorsionType from a single input line.
342    *
343    * @param input  The overall input String.
344    * @param tokens The input String tokenized.
345    * @return a TorsionTorsionType instance.
346    */
347   public static TorsionTorsionType parse(String input, String[] tokens) {
348     if (tokens.length < 8) {
349       logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
350     } else {
351       try {
352         int[] atomClasses = new int[5];
353         for (int i = 0; i < 5; i++) {
354           atomClasses[i] = parseInt(tokens[i + 1]);
355         }
356         int[] gridPoints = new int[2];
357         gridPoints[0] = parseInt(tokens[6]);
358         gridPoints[1] = parseInt(tokens[7]);
359         int points = gridPoints[0] * gridPoints[1];
360         int numTokens = points * 3 + 8;
361         if (tokens.length < numTokens) {
362           logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
363           return null;
364         }
365         double[] torsion1 = new double[points];
366         double[] torsion2 = new double[points];
367         double[] energy = new double[points];
368         int index = 8;
369         for (int i = 0; i < points; i++) {
370           torsion1[i] = parseDouble(tokens[index++]);
371           torsion2[i] = parseDouble(tokens[index++]);
372           energy[i] = parseDouble(tokens[index++]);
373         }
374         return new TorsionTorsionType(atomClasses, gridPoints, torsion1, torsion2, energy);
375       } catch (NumberFormatException e) {
376         String message = "Exception parsing TORTORS type:\n" + input + "\n";
377         logger.log(Level.SEVERE, message, e);
378       }
379     }
380     return null;
381   }
382 
383   /**
384    * Reversed key for the Torsion-Torsion lookup.
385    *
386    * @param c atomClasses
387    * @return lookup key
388    */
389   public static String reverseKey(int[] c) {
390     return c[4] + " " + c[3] + " " + c[2] + " " + c[1] + " " + c[0];
391   }
392 
393   /**
394    * No sorting is done for the Torsion-Torsion lookup.
395    *
396    * @param c atomClasses
397    * @return lookup key
398    */
399   public static String sortKey(int[] c) {
400     return c[0] + " " + c[1] + " " + c[2] + " " + c[3] + " " + c[4];
401   }
402 
403   /**
404    * Solves a system of linear equations for a cyclically tridiagonal, symmetric, positive definite
405    * matrix.
406    *
407    * @param n
408    * @param dm
409    * @param du
410    * @param rs
411    * @param c
412    * @return positive or negative 1.
413    */
414   private static int cytsy(int n, double[] dm, double[] du, double[] cr, double[] rs, double[] c) {
415     if (n < 3) {
416       return -2;
417     }
418 
419     // Factorization of the input matrix.
420     if (cytsyp(n, dm, du, cr) == 1) {
421       // Update and back-substitute as necessary.
422       cytsys(n, dm, du, cr, rs, c);
423       return 1;
424     }
425     return -1;
426   }
427 
428   /**
429    * Finds the Cholesky factors of a cyclically tridiagonal symmetric, positive definite matrix given
430    * by two vectors.
431    *
432    * @param n
433    * @param dm
434    * @param du
435    * @param cr
436    * @return an integer.
437    */
438   private static int cytsyp(int n, double[] dm, double[] du, double[] cr) {
439     double eps = 0.00000001;
440     // Check for n < 3.
441     if (n < 3) {
442       return -2;
443     }
444 
445     // Check if the matrix is positive definite.
446     double row = abs(dm[1]) + abs(du[1]) + abs(du[n]);
447     if (row == 0.0) {
448       return 0;
449     }
450     double d = 1.0 / row;
451     if (dm[1] < 0.0) {
452       return -1;
453     } else if (abs(dm[1]) * d < eps) {
454       return 0;
455     }
456 
457     // Factoring while checking for a positive definite and strong non-singular matrix.
458     double temp1 = du[1];
459     double temp2 = 0.0;
460     du[1] = du[1] / dm[1];
461     cr[1] = du[n] / dm[1];
462     for (int i = 2; i < n; i++) {
463       row = abs(dm[i]) + abs(du[i]) + abs(temp1);
464       if (row == 0.0) {
465         return 0;
466       }
467       d = 1.0 / row;
468       dm[i] = dm[i] - temp1 * du[i - 1];
469       if (dm[i] < 0.0) {
470         return -1;
471       } else if (abs(dm[i]) * d < eps) {
472         return 0;
473       }
474       if (i < (n - 1)) {
475         cr[i] = -temp1 * cr[i - 1] / dm[i];
476         temp1 = du[i];
477         du[i] = du[i] / dm[i];
478       } else {
479         temp2 = du[i];
480         du[i] = (du[i] - temp1 * cr[i - 1]) / dm[i];
481       }
482     }
483     row = abs(du[n]) + abs(dm[n]) + abs(temp2);
484     if (row == 0.0) {
485       return 0;
486     }
487     d = 1.0 / row;
488     dm[n] = dm[n] - dm[n - 1] * du[n - 1] * du[n - 1];
489     temp1 = 0.0;
490     for (int i = 1; i < n - 1; i++) {
491       temp1 = temp1 + dm[i] * cr[i] * cr[i];
492     }
493     dm[n] = dm[n] - temp1;
494     if (dm[n] < 0.0) {
495       return -1;
496     } else if (abs(dm[n]) * d < eps) {
497       return 0;
498     }
499     return 1;
500   }
501 
502   /**
503    * Solves a cyclically tridiagonal linear system given the Cholesky factors.
504    *
505    * @param n
506    * @param dm
507    * @param du
508    * @param cr
509    * @param rs
510    * @param c
511    */
512   private static void cytsys(int n, double[] dm, double[] du, double[] cr, double[] rs, double[] c) {
513     // Updating phase.
514     double temp = rs[1];
515     rs[1] = temp / dm[1];
516     double sum = cr[1] * temp;
517     for (int i = 2; i < n; i++) {
518       temp = rs[i] - du[i - 1] * temp;
519       rs[i] = temp / dm[i];
520       if (i != (n - 1)) {
521         sum = sum + cr[i] * temp;
522       }
523     }
524     temp = rs[n] - du[n - 1] * temp;
525     temp = temp - sum;
526     rs[n] = temp / dm[n];
527 
528     // Back-substitution phase.
529     c[n] = rs[n];
530     c[n - 1] = rs[n - 1] - du[n - 1] * c[n];
531     for (int i = n - 2; i >= 1; i--) {
532       c[i] = rs[i] - du[i] * c[i + 1] - cr[i] * c[n];
533     }
534   }
535 
536   /**
537    * {@inheritDoc}
538    */
539   @Override
540   public int compare(String key1, String key2) {
541     String[] keys1 = key1.split(" ");
542     String[] keys2 = key2.split(" ");
543     int c1 = parseInt(keys1[2]);
544     int c2 = parseInt(keys2[2]);
545     if (c1 < c2) {
546       return -1;
547     } else if (c1 > c2) {
548       return 1;
549     }
550     return 0;
551   }
552 
553   /**
554    * {@inheritDoc}
555    */
556   @Override
557   public boolean equals(Object o) {
558     if (this == o) {
559       return true;
560     }
561     if (o == null || getClass() != o.getClass()) {
562       return false;
563     }
564     TorsionTorsionType torsionTorsionType = (TorsionTorsionType) o;
565     return Arrays.equals(atomClasses, torsionTorsionType.atomClasses);
566   }
567 
568   /**
569    * {@inheritDoc}
570    */
571   @Override
572   public int hashCode() {
573     return Arrays.hashCode(atomClasses);
574   }
575 
576   /**
577    * Increment the atom classes by a value.
578    *
579    * @param increment The increment to add to the atom classes.
580    */
581   public void incrementClasses(int increment) {
582     for (int i = 0; i < atomClasses.length; i++) {
583       atomClasses[i] += increment;
584     }
585     setKey(sortKey(atomClasses));
586   }
587 
588   /**
589    * Remap new atom classes to known internal ones.
590    *
591    * @param typeMap a lookup between new atom types and known atom types.
592    */
593   public void patchClasses(HashMap<AtomType, AtomType> typeMap) {
594     int count = 0;
595     for (AtomType newType : typeMap.keySet()) {
596 
597       for (int atomClass : atomClasses) {
598         if (atomClass == newType.atomClass) {
599           count++;
600         }
601       }
602     }
603     if (count > 0 && count < atomClasses.length) {
604       for (AtomType newType : typeMap.keySet()) {
605         for (int i = 0; i < atomClasses.length; i++) {
606           if (atomClasses[i] == newType.atomClass) {
607             AtomType knownType = typeMap.get(newType);
608             atomClasses[i] = knownType.atomClass;
609           }
610         }
611       }
612       setKey(sortKey(atomClasses));
613     }
614   }
615 
616   /**
617    * {@inheritDoc}
618    *
619    * <p>Nicely formatted torsion-torsion type.
620    */
621   @Override
622   public String toString() {
623     StringBuilder tortorBuffer = new StringBuilder("tortors");
624     for (int i : atomClasses) {
625       tortorBuffer.append(format("  %5d", i));
626     }
627     tortorBuffer.append(format("  %2d  %2d", gridPoints[0], gridPoints[1]));
628     for (int i = 0; i < energy.length; i++) {
629       int nxi = i % nx;
630       int nyi = i / ny;
631       tortorBuffer.append(format(" \\\n  % 6.1f  % 6.1f  % 8.5f", tx[nxi], ty[nyi], energy[i]));
632     }
633     return tortorBuffer.toString();
634   }
635 
636 
637   /**
638    * Create an AmoebaTorsionTorsionForce Element.
639    *
640    * @param doc        the Document instance.
641    * @param forceField the ForceField instance to grab constants from.
642    * @return the AmoebaTorsionTorsionForce Element.
643    */
644   public static Element getXMLForce(Document doc, ForceField forceField) {
645     Map<String, TorsionTorsionType> types = forceField.getTorsionTorsionTypes();
646     if (!types.values().isEmpty()) {
647       Element node = doc.createElement("AmoebaTorsionTorsionForce");
648       int i = 0;
649       for (TorsionTorsionType torsionTorsionType : types.values()) {
650         torsionTorsionType.toXML(doc, node, i);
651         i++;
652       }
653       return node;
654     }
655     return null;
656   }
657 
658   /**
659    * Write TorsionTorsionType to OpenMM XML format.
660    *
661    * @param doc        the Document instance.
662    * @param torTorNode the Element to attach TorsionTorsion and TorsionTorsionGrid to.
663    * @param label      the label used to match TorsionTorsion and TorsionTorsionGrid.
664    */
665   public void toXML(Document doc, Element torTorNode, int label) {
666     Element tortors = doc.createElement("TorsionTorsion");
667     Element ttGrid = doc.createElement("TorsionTorsionGrid");
668 
669     // Set TorsionTorsion node
670     tortors.setAttribute("class1", format("%d", atomClasses[0]));
671     tortors.setAttribute("class2", format("%d", atomClasses[1]));
672     tortors.setAttribute("class3", format("%d", atomClasses[2]));
673     tortors.setAttribute("class4", format("%d", atomClasses[3]));
674     tortors.setAttribute("class5", format("%d", atomClasses[4]));
675     tortors.setAttribute("nx", format("%d", gridPoints[0]));
676     tortors.setAttribute("ny", format("%d", gridPoints[1]));
677 
678     // Set TorsionTorsionGrid
679     ttGrid.setAttribute("nx", format("%d", gridPoints[0]));
680     ttGrid.setAttribute("ny", format("%d", gridPoints[1]));
681     for (int i = 0; i < energy.length; i++) {
682       int nxi = i % nx;
683       int nyi = i / ny;
684       // OpenMM appears to offer some support for storing dE/dX, dE/dY and d2E/dXdY
685       // instead of computing these values on the fly.
686       Element grid = doc.createElement("Grid");
687       grid.setAttribute("angle1", format("%f", tx[nxi]));
688       grid.setAttribute("angle2", format("%f", ty[nyi]));
689       grid.setAttribute("f", format("%f", energy[i] * KCAL_TO_KJ));
690       ttGrid.appendChild(grid);
691     }
692 
693     tortors.setAttribute("grid", String.valueOf(label));
694     ttGrid.setAttribute("grid", String.valueOf(label));
695     torTorNode.appendChild(tortors);
696     torTorNode.appendChild(ttGrid);
697   }
698 
699   /**
700    * Computes the coefficients for an aperiodic interpolating cubic spline.
701    *
702    * @param n
703    * @param x0
704    * @param y0
705    * @param y21
706    * @param y2n
707    * @param s1
708    * @param s2
709    * @param h
710    * @param g
711    * @param dy
712    * @param dla
713    * @param dmu
714    */
715   private void nspline(int n, double[] x0, double[] y0, double y21, double y2n, double[] s1,
716                        double[] s2, double[] h, double[] g, double[] dy, double[] dla, double[] dmu) {
717 
718     // Calculate the intervals.
719     for (int i = 0; i < n; i++) {
720       h[i] = x0[i + 1] - x0[i];
721       dy[i] = (y0[i + 1] - y0[i]) / h[i];
722     }
723 
724     // Get the coeffcients.
725     for (int i = 1; i < n; i++) {
726       dla[i] = h[i] / (h[i] + h[i - 1]);
727       dmu[i] = 1.0 - dla[i];
728       g[i] = 3.0 * (dla[i] * dy[i - 1] + dmu[i] * dy[i]);
729     }
730 
731     // Set the initial value via natural boundary condition.
732     dla[n] = 1.0;
733     dla[0] = 0.0;
734     dmu[n] = 0.0;
735     dmu[0] = 1.0;
736     g[0] = 3.0 * dy[0] - 0.5 * h[0] * y21;
737     g[n] = 3.0 * dy[n - 1] + 0.5 * h[n - 1] * y2n;
738 
739     // Solve the triagonal system of linear equations.
740     dmu[0] = 0.5 * dmu[0];
741     g[0] = 0.5 * g[0];
742     for (int i = 1; i <= n; i++) {
743       double t = 2.0 - dmu[i - 1] * dla[i];
744       dmu[i] = dmu[i] / t;
745       g[i] = (g[i] - g[i - 1] * dla[i]) / t;
746     }
747 
748     for (int i = n - 1; i >= 0; i--) {
749       g[i] = g[i] - dmu[i] * g[i + 1];
750     }
751 
752     // Get the first derivative at each grid point.
753     arraycopy(g, 0, s1, 0, n + 1);
754 
755     // Get the second derivative at each grid point.
756     s2[0] = y21;
757     s2[n] = y2n;
758     for (int i = 1; i < n; i++) {
759       s2[i] =
760           6.0 * (y0[i + 1] - y0[i]) / (h[i] * h[i]) - 4.0 * s1[i] / h[i] - 2.0 * s1[i + 1] / h[i];
761     }
762   }
763 
764   /**
765    * Computes the coefficients for a periodic interpolating cubic spline.
766    *
767    * @param n
768    * @param xn
769    * @param fn
770    * @param b
771    * @param d
772    * @param h
773    * @param du
774    * @param dm
775    * @param rc
776    * @param rs
777    */
778   private void cspline(int n, double[] xn, double[] fn, double[] b, double[] c, double[] d,
779                        double[] h, double[] du, double[] dm, double[] rc, double[] rs) {
780     double eps = 0.000001;
781     if (abs(fn[n] - fn[0]) > eps) {
782       logger.severe("TORTOR values are not periodic.");
783     }
784 
785     // Make the spline exactly periodic making the ends equal to their mean.
786     double mean = 0.5 * (fn[0] + fn[n]);
787     fn[0] = mean;
788     fn[n] = mean;
789 
790     // Set up auxiliary variables and matrix elements on first call.
791     for (int i = 0; i < n; i++) {
792       h[i] = xn[i + 1] - xn[i];
793     }
794     h[n] = h[0];
795 
796     if (n - 1 >= 0) {
797       arraycopy(h, 1, du, 1, n - 1);
798     }
799 
800     du[n] = h[0];
801     for (int i = 1; i <= n; i++) {
802       dm[i] = 2.0 * (h[i - 1] + h[i]);
803     }
804 
805     // Compute the RHS.
806     double temp1 = (fn[1] - fn[0]) / h[0];
807     for (int i = 1; i < n; i++) {
808       double temp2 = (fn[i + 1] - fn[i]) / h[i];
809       rs[i] = 3.0 * (temp2 - temp1);
810       temp1 = temp2;
811     }
812     rs[n] = 3.0 * ((fn[1] - fn[0]) / h[0] - temp1);
813 
814     // Solve the linear system with factorization.
815     if (cytsy(n, dm, du, rc, rs, c) != 1) {
816       return;
817     }
818 
819     // Compute remaining spline coefficients.
820     c[0] = c[n];
821     for (int i = 0; i < n; i++) {
822       b[i] = (fn[i + 1] - fn[i]) / h[i] - h[i] / 3.0 * (c[i + 1] + 2.0 * c[i]);
823       d[i] = (c[i + 1] - c[i]) / (3.0 * h[i]);
824     }
825     b[n] = (fn[1] - fn[n]) / h[n] - h[n] / 3.0 * (c[1] + 2.0 * c[n]);
826   }
827 }