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