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.bonded;
39  
40  import static ffx.numerics.math.DoubleMath.sub;
41  import static org.apache.commons.math3.util.FastMath.acos;
42  import static org.apache.commons.math3.util.FastMath.max;
43  import static org.apache.commons.math3.util.FastMath.min;
44  import static org.apache.commons.math3.util.FastMath.sqrt;
45  import static org.apache.commons.math3.util.FastMath.toDegrees;
46  
47  import ffx.numerics.atomic.AtomicDoubleArray3D;
48  import ffx.potential.parameters.ForceField;
49  import ffx.potential.parameters.TorsionTorsionType;
50  
51  import java.io.Serial;
52  import java.util.List;
53  import java.util.logging.Logger;
54  
55  /**
56   * The TorsionTorsion class represents two adjacent torsional angles formed by five bonded atoms.
57   *
58   * @author Michael J. Schnieders
59   * @since 1.0
60   */
61  public class TorsionTorsion extends BondedTerm implements LambdaInterface {
62  
63    @Serial
64    private static final long serialVersionUID = 1L;
65  
66    private static final Logger logger = Logger.getLogger(TorsionTorsion.class.getName());
67    private static final double[][] wt = {
68        {1.0, 0.0, -3.0, 2.0, 0.0, 0.0, 0.0, 0.0, -3.0, 0.0, 9.0, -6.0, 2.0, 0.0, -6.0, 4.0},
69        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, -9.0, 6.0, -2.0, 0.0, 6.0, -4.0},
70        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 9.0, -6.0, 0.0, 0.0, -6.0, 4.0},
71        {0.0, 0.0, 3.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.0, 6.0, 0.0, 0.0, 6.0, -4.0},
72        {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -3.0, 2.0, -2.0, 0.0, 6.0, -4.0, 1.0, 0.0, -3.0, 2.0},
73        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 3.0, -2.0, 1.0, 0.0, -3.0, 2.0},
74        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.0, 2.0, 0.0, 0.0, 3.0, -2.0},
75        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, -2.0, 0.0, 0.0, -6.0, 4.0, 0.0, 0.0, 3.0, -2.0},
76        {0.0, 1.0, -2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.0, 6.0, -3.0, 0.0, 2.0, -4.0, 2.0},
77        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, -6.0, 3.0, 0.0, -2.0, 4.0, -2.0},
78        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.0, 3.0, 0.0, 0.0, 2.0, -2.0},
79        {0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, -3.0, 0.0, 0.0, -2.0, 2.0},
80        {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, -2.0, 1.0, 0.0, -2.0, 4.0, -2.0, 0.0, 1.0, -2.0, 1.0},
81        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 2.0, -1.0, 0.0, 1.0, -2.0, 1.0},
82        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0},
83        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 2.0, -2.0, 0.0, 0.0, -1.0, 1.0}};
84    /** The two torsions that are coupled. */
85    public final Torsion[] torsions = new Torsion[2];
86    /** The force field Torsion-Torsion type in use. */
87    public TorsionTorsionType torsionTorsionType = null;
88    /** Value of lambda. */
89    private double lambda = 1.0;
90    /** Value of dE/dL. */
91    private double dEdL = 0.0;
92    /** Flag to indicate lambda dependence. */
93    private boolean lambdaTerm = false;
94  
95    /**
96     * Torsion-Torsion constructor.
97     *
98     * @param firstBond a {@link ffx.potential.bonded.Bond} object.
99     * @param angle a {@link ffx.potential.bonded.Angle} object.
100    * @param lastBond a {@link ffx.potential.bonded.Bond} object.
101    * @param reversed a boolean.
102    */
103   public TorsionTorsion(Bond firstBond, Angle angle, Bond lastBond, boolean reversed) {
104     super();
105     atoms = new Atom[5];
106     bonds = new Bond[4];
107     if (!reversed) {
108       atoms[1] = angle.atoms[0];
109       atoms[2] = angle.atoms[1];
110       atoms[3] = angle.atoms[2];
111       atoms[0] = firstBond.get1_2(atoms[1]);
112       atoms[4] = lastBond.get1_2(atoms[3]);
113       bonds[0] = firstBond;
114       bonds[1] = angle.bonds[0];
115       bonds[2] = angle.bonds[1];
116       bonds[3] = lastBond;
117     } else {
118       atoms[1] = angle.atoms[2];
119       atoms[2] = angle.atoms[1];
120       atoms[3] = angle.atoms[0];
121       atoms[0] = lastBond.get1_2(atoms[1]);
122       atoms[4] = firstBond.get1_2(atoms[3]);
123       bonds[0] = lastBond;
124       bonds[1] = angle.bonds[1];
125       bonds[2] = angle.bonds[0];
126       bonds[3] = firstBond;
127     }
128     torsions[0] = atoms[0].getTorsion(atoms[1], atoms[2], atoms[3]);
129     torsions[1] = atoms[4].getTorsion(atoms[3], atoms[2], atoms[1]);
130     setID_Key(false);
131   }
132 
133   /**
134    * torsionTorsionFactory.
135    *
136    * @param firstBond the first Bond.
137    * @param angle the Angle.
138    * @param lastBond the last Bond.
139    * @param forceField the ForceField parameters to apply.
140    * @return the new TorsionTorsion, or null.
141    */
142   public static TorsionTorsion torsionTorsionFactory(Bond firstBond, Angle angle, Bond lastBond,
143       ForceField forceField) {
144     int[] c5 = new int[5];
145     Atom atom1 = angle.atoms[0];
146     Atom atom3 = angle.atoms[2];
147     c5[0] = firstBond.get1_2(atom1).getAtomType().atomClass;
148     c5[1] = atom1.getAtomType().atomClass;
149     c5[2] = angle.atoms[1].getAtomType().atomClass;
150     c5[3] = atom3.getAtomType().atomClass;
151     c5[4] = lastBond.get1_2(atom3).getAtomType().atomClass;
152     String key = TorsionTorsionType.sortKey(c5);
153     boolean reversed = false;
154     TorsionTorsionType torsionTorsionType = forceField.getTorsionTorsionType(key);
155     if (torsionTorsionType == null) {
156       key = TorsionTorsionType.reverseKey(c5);
157       torsionTorsionType = forceField.getTorsionTorsionType(key);
158       reversed = true;
159     }
160     if (torsionTorsionType == null) {
161       return null;
162     }
163     TorsionTorsion torsionTorsion = new TorsionTorsion(firstBond, angle, lastBond, reversed);
164     torsionTorsion.torsionTorsionType = torsionTorsionType;
165     return torsionTorsion;
166   }
167 
168   private static void bcucof(double t1, double t2, double[] e, double[] dx, double[] dy,
169       double[] dxy, double[][] c) {
170 
171     var x16 = new double[16];
172     var cl = new double[16];
173     var t1t2 = t1 * t2;
174 
175     // Pack a temporary vector of corner values.
176     for (int i = 0; i < 4; i++) {
177       x16[i] = e[i];
178       x16[i + 4] = dx[i] * t1;
179       x16[i + 8] = dy[i] * t2;
180       x16[i + 12] = dxy[i] * t1t2;
181     }
182 
183     // Matrix multiply by the stored weight table.
184     for (int i = 0; i < 16; i++) {
185       double xx = 0.0;
186       for (int k = 0; k < 16; k++) {
187         xx += wt[k][i] * x16[k];
188       }
189       cl[i] = xx;
190     }
191 
192     // Unpack the results into the coefficient table.
193     int j = 0;
194     for (int i = 0; i < 4; i++) {
195       for (int k = 0; k < 4; k++) {
196         c[i][k] = cl[j++];
197       }
198     }
199   }
200 
201   /**
202    * {@inheritDoc}
203    *
204    * <p>Evaluate the Torsion-Torsion energy.
205    */
206   @Override
207   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad,
208       AtomicDoubleArray3D lambdaGrad) {
209     energy = 0.0;
210     value = 0.0;
211     dEdL = 0.0;
212     // Only compute this term if at least one atom is being used.
213     if (!getUse()) {
214       return energy;
215     }
216     var atomA = atoms[0];
217     var atomB = atoms[1];
218     var atomC = atoms[2];
219     var atomD = atoms[3];
220     var atomE = atoms[4];
221     var va = atomA.getXYZ();
222     var vb = atomB.getXYZ();
223     var vc = atomC.getXYZ();
224     var vd = atomD.getXYZ();
225     var ve = atomE.getXYZ();
226     var vba = vb.sub(va);
227     var vcb = vc.sub(vb);
228     var vdc = vd.sub(vc);
229     var ved = ve.sub(vd);
230     var vt = vba.X(vcb);
231     var vu = vcb.X(vdc);
232     var vv = vdc.X(ved);
233     var rt2 = vt.length2();
234     var ru2 = vu.length2();
235     var rv2 = vv.length2();
236     var rtru2 = rt2 * ru2;
237     var rurv2 = ru2 * rv2;
238     if (rtru2 != 0.0 && rurv2 != 0.0) {
239       var rtru = sqrt(rt2 * ru2);
240       var rurv = sqrt(ru2 * rv2);
241       var rcb = vcb.length();
242       var cosine1 = min(1.0, max(-1.0, vt.dot(vu) / rtru));
243       var angle1 = toDegrees(acos(cosine1));
244       var sign = vba.dot(vu);
245       if (sign < 0.0) {
246         angle1 *= -1.0;
247       }
248       var rdc = vdc.length();
249       var cosine2 = min(1.0, max(-1.0, vu.dot(vv) / rurv));
250       var angle2 = toDegrees(acos(cosine2));
251       sign = vcb.dot(vv);
252       if (sign < 0.0) {
253         angle2 *= -1.0;
254       }
255       var t1 = angle1;
256       var t2 = angle2;
257       sign = chktor();
258       t1 *= sign;
259       t2 *= sign;
260 
261       // Use bicubic interpolation to compute the spline values.
262       var nx = torsionTorsionType.nx;
263       var ny = torsionTorsionType.ny;
264       var nlow = 0;
265       var nhigh = nx - 1;
266       while (nhigh - nlow > 1) {
267         var nt = (nhigh + nlow) / 2;
268         if (torsionTorsionType.tx[nt] > t1) {
269           nhigh = nt;
270         } else {
271           nlow = nt;
272         }
273       }
274       var xlow = nlow;
275       nlow = 0;
276       nhigh = ny - 1;
277       while (nhigh - nlow > 1) {
278         var nt = (nhigh + nlow) / 2;
279         if (torsionTorsionType.ty[nt] > t2) {
280           nhigh = nt;
281         } else {
282           nlow = nt;
283         }
284       }
285       var ylow = nlow;
286       var x1l = torsionTorsionType.tx[xlow];
287       var x1u = torsionTorsionType.tx[xlow + 1];
288       var y1l = torsionTorsionType.ty[ylow];
289       var y1u = torsionTorsionType.ty[ylow + 1];
290       var pos2 = (ylow + 1) * nx + xlow;
291       var pos1 = pos2 - nx;
292 
293       // Array of 4 spline energies surrounding the actual Torsion-Torsion location.
294       var e = new double[4];
295       e[0] = torsionTorsionType.energy[pos1];
296       e[1] = torsionTorsionType.energy[pos1 + 1];
297       e[2] = torsionTorsionType.energy[pos2 + 1];
298       e[3] = torsionTorsionType.energy[pos2];
299       // Array of 4 spline x-gradients surrounding the actual Torsion-Torsion location.
300       var dx = new double[4];
301       dx[0] = torsionTorsionType.dx[pos1];
302       dx[1] = torsionTorsionType.dx[pos1 + 1];
303       dx[2] = torsionTorsionType.dx[pos2 + 1];
304       dx[3] = torsionTorsionType.dx[pos2];
305       // Array of 4 spline y-gradients surrounding the actual Torsion-Torsion location.
306       var dy = new double[4];
307       dy[0] = torsionTorsionType.dy[pos1];
308       dy[1] = torsionTorsionType.dy[pos1 + 1];
309       dy[2] = torsionTorsionType.dy[pos2 + 1];
310       dy[3] = torsionTorsionType.dy[pos2];
311       // Array of 4 spline xy-gradients surrounding the actual Torsion-Torsion location.
312       var dxy = new double[4];
313       dxy[0] = torsionTorsionType.dxy[pos1];
314       dxy[1] = torsionTorsionType.dxy[pos1 + 1];
315       dxy[2] = torsionTorsionType.dxy[pos2 + 1];
316       dxy[3] = torsionTorsionType.dxy[pos2];
317       if (!gradient && !lambdaTerm) {
318         var bcu = bcuint(x1l, x1u, y1l, y1u, t1, t2, e, dx, dy, dxy);
319         energy = torsionTorsionType.torTorUnit * bcu * lambda;
320         dEdL = torsionTorsionType.torTorUnit * bcu;
321       } else {
322         var ansy = new double[2];
323         var bcu1 = bcuint1(x1l, x1u, y1l, y1u, t1, t2, e, dx, dy, dxy, ansy);
324         energy = torsionTorsionType.torTorUnit * bcu1 * lambda;
325         dEdL = torsionTorsionType.torTorUnit * bcu1;
326         var dedang1 = sign * torsionTorsionType.torTorUnit * toDegrees(ansy[0]) * lambda;
327         var dedang2 = sign * torsionTorsionType.torTorUnit * toDegrees(ansy[1]) * lambda;
328         // Derivative components for the first angle.
329         var vca = vc.sub(va);
330         var vdb = vd.sub(vb);
331         var vdt = vt.X(vcb).scaleI(dedang1 / (rt2 * rcb));
332         var vdu = vu.X(vcb).scaleI(-dedang1 / (ru2 * rcb));
333         // Gradients on each atom.
334         var ga = vdt.X(vcb);
335         var gb = vca.X(vdt).addI(vdu.X(vdc));
336         var gc = vdb.X(vdu).addI(vdt.X(vba));
337         var gd = vdu.X(vcb);
338         var ia = atomA.getIndex() - 1;
339         var ib = atomB.getIndex() - 1;
340         var ic = atomC.getIndex() - 1;
341         var id = atomD.getIndex() - 1;
342         var ie = atomE.getIndex() - 1;
343         if (lambdaTerm) {
344           lambdaGrad.add(threadID, ia, ga);
345           lambdaGrad.add(threadID, ib, gb);
346           lambdaGrad.add(threadID, ic, gc);
347           lambdaGrad.add(threadID, id, gd);
348         }
349         if (gradient) {
350           grad.add(threadID, ia, ga.scaleI(lambda));
351           grad.add(threadID, ib, gb.scaleI(lambda));
352           grad.add(threadID, ic, gc.scaleI(lambda));
353           grad.add(threadID, id, gd.scaleI(lambda));
354         }
355         // Derivative components for the 2nd angle.
356         var vec = ve.sub(vc);
357         vdt = vu.X(vdc).scaleI(dedang2 / (ru2 * rdc));
358         vdu = vv.X(vdc).scaleI(-dedang2 / (rv2 * rdc));
359         // Gradients on each atom.
360         gb = vdt.X(vdc);
361         gc = vdb.X(vdt).addI(vdu.X(ved));
362         gd = vdt.X(vcb).addI(vec.X(vdu));
363         var ge = vdu.X(vdc);
364         if (lambdaTerm) {
365           lambdaGrad.add(threadID, ib, gb);
366           lambdaGrad.add(threadID, ic, gc);
367           lambdaGrad.add(threadID, id, gd);
368           lambdaGrad.add(threadID, ie, ge);
369         }
370         if (gradient) {
371           grad.add(threadID, ib, gb.scaleI(lambda));
372           grad.add(threadID, ic, gc.scaleI(lambda));
373           grad.add(threadID, id, gd.scaleI(lambda));
374           grad.add(threadID, ie, ge.scaleI(lambda));
375         }
376       }
377     }
378     return energy;
379   }
380 
381   /**
382    * getChiralAtom.
383    *
384    * @return a {@link ffx.potential.bonded.Atom} object.
385    */
386   public Atom getChiralAtom() {
387     Atom atom = null;
388     List<Bond> bnds = atoms[2].getBonds();
389 
390     // To be chiral, the central atom must have 4 bonds.
391     if (bnds.size() == 4) {
392       // Find the two atoms that are not part of the dihedral.
393       Atom atom1 = null;
394       Atom atom2 = null;
395       for (Bond b : bnds) {
396         Atom a = b.get1_2(atoms[2]);
397         if (a != atoms[1] && a != atoms[3]) {
398           if (atom1 == null) {
399             atom1 = a;
400           } else {
401             atom2 = a;
402           }
403         }
404       }
405       /*
406        Choose atom1 or atom2 to use for the chiral check, depending on
407        their atom types and atomic number.
408       */
409       if (atom1.getType() > atom2.getType()) {
410         atom = atom1;
411       }
412       if (atom2.getType() > atom1.getType()) {
413         atom = atom2;
414       }
415       if (atom1.getAtomicNumber() > atom2.getAtomicNumber()) {
416         atom = atom1;
417       }
418       if (atom2.getAtomicNumber() > atom1.getAtomicNumber()) {
419         atom = atom2;
420       }
421     }
422     return atom;
423   }
424 
425   /** {@inheritDoc} */
426   @Override
427   public double getLambda() {
428     return lambda;
429   }
430 
431   /** {@inheritDoc} */
432   @Override
433   public void setLambda(double lambda) {
434     if (applyAllLambda()) {
435       lambdaTerm = true;
436       this.lambda = lambda;
437     } else {
438       lambdaTerm = false;
439       this.lambda = 1.0;
440     }
441   }
442 
443   /** {@inheritDoc} */
444   @Override
445   public double getd2EdL2() {
446     return 0.0;
447   }
448 
449   /** {@inheritDoc} */
450   @Override
451   public double getdEdL() {
452     if (lambdaTerm) {
453       return dEdL;
454     } else {
455       return 0.0;
456     }
457   }
458 
459   /** {@inheritDoc} */
460   @Override
461   public void getdEdXdL(double[] gradient) {
462     // This chain rule term is zero.
463   }
464 
465   /** Log details for this Torsion-Torsion energy term. */
466   public void log() {
467     logger.info(String.format(" %s %6d-%s %6d-%s %6d-%s %6d-%s %10.4f", "Torsional-Torsion",
468         atoms[0].getIndex(), atoms[0].getAtomType().name, atoms[1].getIndex(),
469         atoms[1].getAtomType().name, atoms[2].getIndex(), atoms[2].getAtomType().name,
470         atoms[3].getIndex(), atoms[3].getAtomType().name, energy));
471   }
472 
473   /**
474    * {@inheritDoc}
475    *
476    * <p>Overridden toString Method returns the Term's id.
477    */
478   @Override
479   public String toString() {
480     return String.format("%s  (%7.2f,%7.2f,%7.2f)", id, torsions[0].value, torsions[1].value,
481         energy);
482   }
483 
484   /**
485    * Check for inversion of the central atom if it is chiral.
486    *
487    * @return The sign convention - if negative the torsion angle signs are inverted.
488    */
489   private double chktor() {
490 
491     // Vector from the central atom to site 0.
492     var vc0 = new double[3];
493     // Vector from the central atom to site 1.
494     var vc1 = new double[3];
495     // Vector from the central atom to site 2.
496     var vc2 = new double[3];
497 
498     List<Bond> bonds = atoms[2].getBonds();
499     // To be chiral, the central atom must have 4 bonds.
500     if (bonds.size() == 4) {
501       // Find the two atoms that are not part of the dihedral.
502       Atom atom1 = null;
503       Atom atom2 = null;
504       for (Bond b : bonds) {
505         Atom a = b.get1_2(atoms[2]);
506         if (a != atoms[1] && a != atoms[3]) {
507           if (atom1 == null) {
508             atom1 = a;
509           } else {
510             atom2 = a;
511           }
512         }
513       }
514       /*
515        Choose atom1 or atom2 to use for the chiral check, depending on
516        their atom types and atomic number.
517       */
518       Atom atom = null;
519       if (atom1.getType() > atom2.getType()) {
520         atom = atom1;
521       }
522       if (atom2.getType() > atom1.getType()) {
523         atom = atom2;
524       }
525       if (atom1.getAtomicNumber() > atom2.getAtomicNumber()) {
526         atom = atom1;
527       }
528       if (atom2.getAtomicNumber() > atom1.getAtomicNumber()) {
529         atom = atom2;
530       }
531 
532       // Compute the signed parallelpiped volume at the central site.
533       if (atom != null) {
534         var ad = new double[3];
535         var a1 = new double[3];
536         var a2 = new double[3];
537         var a3 = new double[3];
538         atom.getXYZ(ad);
539         atoms[1].getXYZ(a1);
540         atoms[2].getXYZ(a2);
541         atoms[3].getXYZ(a3);
542         sub(ad, a2, vc0);
543         sub(a1, a2, vc1);
544         sub(a3, a2, vc2);
545         double volume = vc0[0] * (vc1[1] * vc2[2] - vc1[2] * vc2[1])
546             + vc1[0] * (vc2[1] * vc0[2] - vc2[2] * vc0[1]) + vc2[0] * (vc0[1] * vc1[2]
547             - vc0[2] * vc1[1]);
548         if (volume < 0.0) {
549           return -1.0;
550         }
551       }
552     }
553     return 1.0;
554   }
555 
556   /**
557    * bcuint
558    *
559    * @param x1l a double.
560    * @param x1u a double.
561    * @param y1l a double.
562    * @param y1u a double.
563    * @param t1 a double.
564    * @param t2 a double.
565    * @param e an array of {@link double} objects.
566    * @param dx an array of {@link double} objects.
567    * @param dy an array of {@link double} objects.
568    * @param dxy an array of {@link double} objects.
569    * @return a double.
570    */
571   private double bcuint(double x1l, double x1u, double y1l, double y1u, double t1, double t2,
572       double[] e, double[] dx, double[] dy, double[] dxy) {
573 
574     var c = new double[4][4];
575     var deltax = x1u - x1l;
576     var deltay = y1u - y1l;
577     bcucof(deltax, deltay, e, dx, dy, dxy, c);
578     var tx = (t1 - x1l) / deltax;
579     var ux = (t2 - y1l) / deltay;
580     var ret = 0.0;
581     for (int i = 3; i >= 0; i--) {
582       ret = tx * ret + ((c[i][3] * ux + c[i][2]) * ux + c[i][1]) * ux + c[i][0];
583     }
584     return ret;
585   }
586 
587   /**
588    * bcuint1
589    *
590    * @param x1l a double.
591    * @param x1u a double.
592    * @param y1l a double.
593    * @param y1u a double.
594    * @param t1 a double.
595    * @param t2 a double.
596    * @param e an array of {@link double} objects.
597    * @param dx an array of {@link double} objects.
598    * @param dy an array of {@link double} objects.
599    * @param dxy an array of {@link double} objects.
600    * @param ansy an array of {@link double} objects.
601    * @return a double.
602    */
603   private double bcuint1(double x1l, double x1u, double y1l, double y1u, double t1, double t2,
604       double[] e, double[] dx, double[] dy, double[] dxy, double[] ansy) {
605 
606     var c = new double[4][4];
607     var deltax = x1u - x1l;
608     var deltay = y1u - y1l;
609     bcucof(deltax, deltay, e, dx, dy, dxy, c);
610     var tx = (t1 - x1l) / deltax;
611     var ux = (t2 - y1l) / deltay;
612     var ret = 0.0;
613     ansy[0] = 0.0;
614     ansy[1] = 0.0;
615     for (int i = 3; i >= 0; i--) {
616       ret = tx * ret + ((c[i][3] * ux + c[i][2]) * ux + c[i][1]) * ux + c[i][0];
617       ansy[0] = ux * ansy[0] + (3.0 * c[3][i] * tx + 2.0 * c[2][i]) * tx + c[1][i];
618       ansy[1] = tx * ansy[1] + (3.0 * c[i][3] * ux + 2.0 * c[i][2]) * ux + c[i][1];
619     }
620     ansy[0] /= deltax;
621     ansy[1] /= deltay;
622     return ret;
623   }
624 }