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.numerics.integrate;
39  
40  import java.text.DecimalFormat;
41  
42  import static org.apache.commons.math3.util.FastMath.abs;
43  import static org.apache.commons.math3.util.FastMath.cos;
44  import static org.apache.commons.math3.util.FastMath.sin;
45  
46  /**
47   * This program integrates using three methods: the trapezoidal method, Simpson's Three Point
48   * Integration, and Boole's Five Point Integration
49   *
50   * @author Claire O'Connell
51   */
52  public class Integration {
53  
54    private static final double[] x = new double[202];
55  
56    static {
57      x[0] = 0;
58      for (int i = 1; i < 201; i++) {
59        x[i] = .0025 + .005 * (i - 1);
60      }
61      x[201] = 1;
62    }
63  
64    private Integration() {
65      // Prevent instantiation.
66    }
67  
68  
69    /**
70     * averageIntegral.
71     *
72     * @param leftInt  a double.
73     * @param rightInt a double.
74     * @return a double.
75     */
76    public static double averageIntegral(double leftInt, double rightInt) {
77      return (leftInt + rightInt) / 2.0;
78    }
79  
80    /**
81     * generateTestData_v1.
82     *
83     * @return an array of double values.
84     */
85    public static double[] generateTestData_v1() {
86      double[] y = new double[202];
87  
88      for (int i = 0; i < 202; i++) {
89        y[i] = 10 * sin(6 * x[i]) - 7 * cos(5 * x[i]) + 11 * sin(8 * x[i]);
90      }
91  
92      return y;
93    }
94  
95    /**
96     * halfBinComposite.
97     *
98     * @param inputData an array of double values.
99     * @param mode      the integration mode.
100    * @param side      a {@link java.lang.String} object.
101    * @return a double.
102    */
103   public static double halfBinComposite(double[] inputData, int mode, String side) {
104     int n = inputData.length;
105     double halfBinComposite = 0;
106     // Split by side first, then integration type
107     if (side.equalsIgnoreCase("left")) {
108       // Using trapezoidal integral for lower half bin
109       double lowerHalfBin = (inputData[1] + inputData[0]) / 2.0 * (x[1] - x[0]);
110       halfBinComposite += lowerHalfBin;
111       switch (mode) {
112         // Case 1 is the Simpson's method, uses trapezoid on right for bin left out of Simpson's
113         case 1 -> {
114           double upperTrapArea = (inputData[n - 3] + inputData[n - 2]) / 2.0 * (x[n - 2] - x[n - 3]);
115           halfBinComposite += upperTrapArea;
116         }
117         // Case 2 is the Boole's method, uses Simpsons and trapezoidal integral on right to cover
118         // remaining bins
119         case 2 -> {
120           double upperSimpson =
121               (1.0 / 3.0)
122                   * (x[n - 4] - x[n - 5])
123                   * (inputData[n - 5] + 4 * inputData[n - 4] + inputData[n - 3]);
124           halfBinComposite += upperSimpson;
125           double upperTrapArea = (inputData[n - 3] + inputData[n - 2]) / 2.0 * (x[n - 2] - x[n - 3]);
126           halfBinComposite += upperTrapArea;
127         }
128       }
129     } else if (side.equalsIgnoreCase("right")) {
130       // Upper half bin calculated with trapezoid
131       double upperHalfBin = (inputData[n - 1] + inputData[n - 2]) / 2.0 * (x[n - 1] - x[n - 2]);
132       halfBinComposite += upperHalfBin;
133       switch (mode) {
134         // Case 1 is the Simpson's method, uses trapezoid on left for bin left out of Simpson's
135         case 1:
136           double lowerTrapArea = (inputData[1] + inputData[2]) / 2.0 * (x[2] - x[1]);
137           halfBinComposite += lowerTrapArea;
138           break;
139         // Case 2 is the Boole's method, uses Simpsons and trapezoidal integral on left to cover
140         // remaining bins
141         case 2:
142           lowerTrapArea = (inputData[1] + inputData[2]) / 2.0 * (x[2] - x[1]);
143           halfBinComposite += lowerTrapArea;
144           double lowerSimpson =
145               (1.0 / 3.0) * (x[3] - x[2]) * (inputData[2] + 4 * inputData[3] + inputData[4]);
146           halfBinComposite += lowerSimpson;
147           break;
148       }
149     }
150     return halfBinComposite;
151   }
152 
153   /**
154    * leftBoole.
155    *
156    * @param inputData an array of double values.
157    * @return a double.
158    */
159   public static double leftBoole(double[] inputData) {
160     int n = inputData.length;
161     double normalBoole = 0;
162     for (int a = 1; a < n - 5; a += 4) {
163       double area =
164           (2.0 / 45.0)
165               * (x[a + 1] - x[a])
166               * (7 * inputData[a]
167               + 32 * inputData[a + 1]
168               + 12 * inputData[a + 2]
169               + 32 * inputData[a + 3]
170               + 7 * inputData[a + 4]);
171       normalBoole += area;
172     }
173 
174     return normalBoole;
175   }
176 
177   /**
178    * leftRectangularMethod.
179    *
180    * @param inputData an array of double values.
181    * @return a double.
182    */
183   public static double leftRectangularMethod(double[] inputData) {
184     int n = inputData.length;
185     double[] y = generateTestData_v1();
186     double rectangularIntegral = 0;
187     for (int a = 0; a < n - 2; a++) {
188       double area = (x[a + 1] - x[a]) * y[a];
189       rectangularIntegral += area;
190     }
191     return rectangularIntegral;
192   }
193 
194   /**
195    * leftSimpsons.
196    *
197    * @param inputData an array of double values.
198    * @return a double.
199    */
200   public static double leftSimpsons(double[] inputData) {
201     int n = inputData.length;
202     double normalSimpsons = 0;
203     for (int a = 1; a < n - 4; a += 2) {
204       double area =
205           (1.0 / 3.0)
206               * (x[a + 1] - x[a])
207               * (inputData[a] + 4 * inputData[a + 1] + inputData[a + 2]);
208       normalSimpsons += area;
209     }
210     return normalSimpsons;
211   }
212 
213   /**
214    * leftTrapInput.
215    *
216    * @param inputData an array of double values.
217    * @return a double.
218    */
219   public static double leftTrapInput(double[] inputData) {
220     int n = x.length;
221     double sum = 0;
222     double total = 0;
223     double trapIntegral = 0;
224     for (int a = 0; a < n - 2; a++) {
225       if (a > 0) {
226         double area = (inputData[a + 1] + inputData[a]) / (double) 2 * (x[a + 1] - x[a]);
227         sum = area + total;
228         total = sum;
229       }
230       if (a == n - 3) {
231         trapIntegral = sum;
232       }
233       if (a == 0) {
234         total = (inputData[a + 1] + inputData[a]) / (double) 2 * (x[a + 1] - x[a]);
235       }
236     }
237 
238     return trapIntegral;
239   }
240 
241   /**
242    * main.
243    *
244    * @param args an array of {@link java.lang.String} objects.
245    */
246   public static void main(String[] args) {
247     double testAnswer;
248     double testTrap, testTrapRight, avgTrap, avgTrapError;
249     double testSimp, testSimpRight, avgSimp, avgSimpError;
250     double testBoole, testBooleRight, avgBoole, avgBooleError;
251     double testRect, testRectRight, avgRect, avgRectError;
252     DecimalFormat decimalFormat = new DecimalFormat("#.00");
253 
254     testAnswer = 2.98393938659796;
255     System.out.print(" The test case answer is " + testAnswer + "\n\n");
256 
257     testRect = leftRectangularMethod(generateTestData_v1());
258     testRectRight = rightRectangularMethod(generateTestData_v1());
259     avgRect = (testRect + testRectRight) / 2.0;
260     avgRectError = abs(testAnswer - avgRect) / testAnswer * 100;
261 
262     testTrap = leftTrapInput(generateTestData_v1());
263     testTrapRight = rightTrapInput(generateTestData_v1());
264     avgTrap = (testTrap + testTrapRight) / 2.0;
265     avgTrapError = abs(avgTrap - testAnswer) / testAnswer * 100;
266 
267     testSimp =
268         leftSimpsons(generateTestData_v1()) + halfBinComposite(generateTestData_v1(), 1, "left");
269     testSimpRight =
270         rightSimpsons(generateTestData_v1()) + halfBinComposite(generateTestData_v1(), 1, "right");
271     avgSimp = (testSimp + testSimpRight) / 2.0;
272     avgSimpError = abs(testAnswer - avgSimp) / testAnswer * 100;
273 
274     testBoole =
275         leftBoole(generateTestData_v1()) + halfBinComposite(generateTestData_v1(), 2, "left");
276     testBooleRight =
277         rightBoole(generateTestData_v1()) + halfBinComposite(generateTestData_v1(), 2, "right");
278     avgBoole = (testBoole + testBooleRight) / 2.0;
279     avgBooleError = abs(testAnswer - avgBoole) / testAnswer * 100;
280 
281     // Average integrals
282     System.out.print(" Average integrals \n\n");
283     System.out.print(" Average rectangular " + avgRect + "\n");
284     System.out.print(" Average trap " + avgTrap + "\n");
285     System.out.print(" Average Simpsons " + avgSimp + "\n");
286     System.out.print(" Average Boole " + avgBoole + "\n");
287 
288     // Average integral errors
289     System.out.print("\n Average integral error \n\n");
290     System.out.print(" Average rectangular error " + decimalFormat.format(avgRectError) + "%\n");
291     System.out.print(" Average Trapezoidal error  " + decimalFormat.format(avgTrapError) + "%\n");
292     System.out.print(" Average Simpsons error " + decimalFormat.format(avgSimpError) + "%\n");
293     System.out.print(" Average Boole error " + decimalFormat.format(avgBooleError) + "%\n");
294   }
295 
296   /**
297    * rightBoole.
298    *
299    * @param inputData an array of double values.
300    * @return a double.
301    */
302   public static double rightBoole(double[] inputData) {
303     int n = inputData.length;
304     double normalBoole = 0;
305     for (int a = 4; a < n - 5; a += 4) {
306       // Simpsons and trapezoid + lower bin on left
307       double area =
308           (2.0 / 45.0)
309               * (x[a + 1] - x[a])
310               * (7 * inputData[a]
311               + 32 * inputData[a + 1]
312               + 12 * inputData[a + 2]
313               + 32 * inputData[a + 3]
314               + 7 * inputData[a + 4]);
315       normalBoole += area;
316     }
317 
318     return normalBoole;
319   }
320 
321   /**
322    * rightRectangularMethod.
323    *
324    * @param inputData an array of double values.
325    * @return a double.
326    */
327   public static double rightRectangularMethod(double[] inputData) {
328     int n = inputData.length;
329     double rectangularIntegral = 0;
330     double[] y = generateTestData_v1();
331     for (int a = 1; a < n - 1; a++) {
332       double area = (x[a + 1] - x[a]) * y[a];
333       rectangularIntegral += area;
334     }
335     return rectangularIntegral;
336   }
337 
338   /**
339    * rightSimpsons.
340    *
341    * @param inputData an array of double values.
342    * @return a double.
343    */
344   public static double rightSimpsons(double[] inputData) {
345     int n = inputData.length;
346     double normalSimpsons = 0;
347     for (int a = 2; a < n - 3; a += 2) {
348       // Extra trap on lower edge so right edge of rightmost bin aligns with the upper half bin
349       double area =
350           (1.0 / 3.0)
351               * (x[a + 1] - x[a])
352               * (inputData[a] + 4 * inputData[a + 1] + inputData[a + 2]);
353       normalSimpsons += area;
354     }
355     return normalSimpsons;
356   }
357 
358   /**
359    * rightTrapInput.
360    *
361    * @param inputData an array of double values.
362    * @return a double.
363    */
364   public static double rightTrapInput(double[] inputData) {
365     int n = x.length;
366     double sum = 0;
367     double total = 0;
368     double trapIntegral = 0;
369     for (int a = 1; a < n - 1; a++) {
370       if (a > 1) {
371         double area = (inputData[a + 1] + inputData[a]) / 2.0 * (x[a + 1] - x[a]);
372         sum = area + total;
373         total = sum;
374       }
375       if (a == n - 2) {
376         trapIntegral = sum;
377       }
378       if (a == 1) {
379         total = (inputData[a + 1] + inputData[a]) / 2.0 * (x[a + 1] - x[a]);
380       }
381     }
382 
383     return trapIntegral;
384   }
385 }