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