View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math.analysis;
18  
19  import java.io.Serializable;
20  
21  import org.apache.commons.math.DuplicateSampleAbscissaException;
22  import org.apache.commons.math.FunctionEvaluationException;
23  
24  /**
25   * Implements the representation of a real polynomial function in
26   * <a href="http://mathworld.wolfram.com/LagrangeInterpolatingPolynomial.html">
27   * Lagrange Form</a>. For reference, see <b>Introduction to Numerical
28   * Analysis</b>, ISBN 038795452X, chapter 2.
29   * <p>
30   * The approximated function should be smooth enough for Lagrange polynomial
31   * to work well. Otherwise, consider using splines instead.</p>
32   *
33   * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $
34   * @since 1.2
35   */
36  public class PolynomialFunctionLagrangeForm implements UnivariateRealFunction,
37      Serializable {
38  
39      /** serializable version identifier */
40      static final long serialVersionUID = -3965199246151093920L;
41  
42      /**
43       * The coefficients of the polynomial, ordered by degree -- i.e.
44       * coefficients[0] is the constant term and coefficients[n] is the 
45       * coefficient of x^n where n is the degree of the polynomial.
46       */
47      private double coefficients[];
48  
49      /**
50       * Interpolating points (abscissas) and the function values at these points.
51       */
52      private double x[], y[];
53  
54      /**
55       * Whether the polynomial coefficients are available.
56       */
57      private boolean coefficientsComputed;
58  
59      /**
60       * Construct a Lagrange polynomial with the given abscissas and function
61       * values. The order of interpolating points are not important.
62       * <p>
63       * The constructor makes copy of the input arrays and assigns them.</p>
64       * 
65       * @param x interpolating points
66       * @param y function values at interpolating points
67       * @throws IllegalArgumentException if input arrays are not valid
68       */
69      PolynomialFunctionLagrangeForm(double x[], double y[]) throws
70          IllegalArgumentException {
71  
72          verifyInterpolationArray(x, y);
73          this.x = new double[x.length];
74          this.y = new double[y.length];
75          System.arraycopy(x, 0, this.x, 0, x.length);
76          System.arraycopy(y, 0, this.y, 0, y.length);
77          coefficientsComputed = false;
78      }
79  
80      /**
81       * Calculate the function value at the given point.
82       *
83       * @param z the point at which the function value is to be computed
84       * @return the function value
85       * @throws FunctionEvaluationException if a runtime error occurs
86       * @see UnivariateRealFunction#value(double)
87       */
88      public double value(double z) throws FunctionEvaluationException {
89          try {
90              return evaluate(x, y, z);
91          } catch (DuplicateSampleAbscissaException e) {
92              throw new FunctionEvaluationException(z, e.getPattern(), e.getArguments(), e);
93          }
94      }
95  
96      /**
97       * Returns the degree of the polynomial.
98       * 
99       * @return the degree of the polynomial
100      */
101     public int degree() {
102         return x.length - 1;
103     }
104 
105     /**
106      * Returns a copy of the interpolating points array.
107      * <p>
108      * Changes made to the returned copy will not affect the polynomial.</p>
109      * 
110      * @return a fresh copy of the interpolating points array
111      */
112     public double[] getInterpolatingPoints() {
113         double[] out = new double[x.length];
114         System.arraycopy(x, 0, out, 0, x.length);
115         return out;
116     }
117 
118     /**
119      * Returns a copy of the interpolating values array.
120      * <p>
121      * Changes made to the returned copy will not affect the polynomial.</p>
122      * 
123      * @return a fresh copy of the interpolating values array
124      */
125     public double[] getInterpolatingValues() {
126         double[] out = new double[y.length];
127         System.arraycopy(y, 0, out, 0, y.length);
128         return out;
129     }
130 
131     /**
132      * Returns a copy of the coefficients array.
133      * <p>
134      * Changes made to the returned copy will not affect the polynomial.</p>
135      * 
136      * @return a fresh copy of the coefficients array
137      */
138     public double[] getCoefficients() {
139         if (!coefficientsComputed) {
140             computeCoefficients();
141         }
142         double[] out = new double[coefficients.length];
143         System.arraycopy(coefficients, 0, out, 0, coefficients.length);
144         return out;
145     }
146 
147     /**
148      * Evaluate the Lagrange polynomial using 
149      * <a href="http://mathworld.wolfram.com/NevillesAlgorithm.html">
150      * Neville's Algorithm</a>. It takes O(N^2) time.
151      * <p>
152      * This function is made public static so that users can call it directly
153      * without instantiating PolynomialFunctionLagrangeForm object.</p>
154      *
155      * @param x the interpolating points array
156      * @param y the interpolating values array
157      * @param z the point at which the function value is to be computed
158      * @return the function value
159      * @throws DuplicateSampleAbscissaException if the sample has duplicate abscissas
160      * @throws IllegalArgumentException if inputs are not valid
161      */
162     public static double evaluate(double x[], double y[], double z) throws
163         DuplicateSampleAbscissaException, IllegalArgumentException {
164 
165         int i, j, n, nearest = 0;
166         double value, c[], d[], tc, td, divider, w, dist, min_dist;
167 
168         verifyInterpolationArray(x, y);
169 
170         n = x.length;
171         c = new double[n];
172         d = new double[n];
173         min_dist = Double.POSITIVE_INFINITY;
174         for (i = 0; i < n; i++) {
175             // initialize the difference arrays
176             c[i] = y[i];
177             d[i] = y[i];
178             // find out the abscissa closest to z
179             dist = Math.abs(z - x[i]);
180             if (dist < min_dist) {
181                 nearest = i;
182                 min_dist = dist;
183             }
184         }
185 
186         // initial approximation to the function value at z
187         value = y[nearest];
188 
189         for (i = 1; i < n; i++) {
190             for (j = 0; j < n-i; j++) {
191                 tc = x[j] - z;
192                 td = x[i+j] - z;
193                 divider = x[j] - x[i+j];
194                 if (divider == 0.0) {
195                     // This happens only when two abscissas are identical.
196                     throw new DuplicateSampleAbscissaException(x[i], i, i+j);
197                 }
198                 // update the difference arrays
199                 w = (c[j+1] - d[j]) / divider;
200                 c[j] = tc * w;
201                 d[j] = td * w;
202             }
203             // sum up the difference terms to get the final value
204             if (nearest < 0.5*(n-i+1)) {
205                 value += c[nearest];    // fork down
206             } else {
207                 nearest--;
208                 value += d[nearest];    // fork up
209             }
210         }
211 
212         return value;
213     }
214 
215     /**
216      * Calculate the coefficients of Lagrange polynomial from the
217      * interpolation data. It takes O(N^2) time.
218      * <p>
219      * Note this computation can be ill-conditioned. Use with caution
220      * and only when it is necessary.</p>
221      *
222      * @throws ArithmeticException if any abscissas coincide
223      */
224     protected void computeCoefficients() throws ArithmeticException {
225         int i, j, n;
226         double c[], tc[], d, t;
227 
228         n = degree() + 1;
229         coefficients = new double[n];
230         for (i = 0; i < n; i++) {
231             coefficients[i] = 0.0;
232         }
233 
234         // c[] are the coefficients of P(x) = (x-x[0])(x-x[1])...(x-x[n-1])
235         c = new double[n+1];
236         c[0] = 1.0;
237         for (i = 0; i < n; i++) {
238             for (j = i; j > 0; j--) {
239                 c[j] = c[j-1] - c[j] * x[i];
240             }
241             c[0] *= (-x[i]);
242             c[i+1] = 1;
243         }
244 
245         tc = new double[n];
246         for (i = 0; i < n; i++) {
247             // d = (x[i]-x[0])...(x[i]-x[i-1])(x[i]-x[i+1])...(x[i]-x[n-1])
248             d = 1;
249             for (j = 0; j < n; j++) {
250                 if (i != j) {
251                     d *= (x[i] - x[j]);
252                 }
253             }
254             if (d == 0.0) {
255                 // This happens only when two abscissas are identical.
256                 throw new ArithmeticException
257                     ("Identical abscissas cause division by zero.");
258             }
259             t = y[i] / d;
260             // Lagrange polynomial is the sum of n terms, each of which is a
261             // polynomial of degree n-1. tc[] are the coefficients of the i-th
262             // numerator Pi(x) = (x-x[0])...(x-x[i-1])(x-x[i+1])...(x-x[n-1]).
263             tc[n-1] = c[n];     // actually c[n] = 1
264             coefficients[n-1] += t * tc[n-1];
265             for (j = n-2; j >= 0; j--) {
266                 tc[j] = c[j+1] + tc[j+1] * x[i];
267                 coefficients[j] += t * tc[j];
268             }
269         }
270 
271         coefficientsComputed = true;
272     }
273 
274     /**
275      * Verifies that the interpolation arrays are valid.
276      * <p>
277      * The interpolating points must be distinct. However it is not
278      * verified here, it is checked in evaluate() and computeCoefficients().</p>
279      * 
280      * @param x the interpolating points array
281      * @param y the interpolating values array
282      * @throws IllegalArgumentException if not valid
283      * @see #evaluate(double[], double[], double)
284      * @see #computeCoefficients()
285      */
286     protected static void verifyInterpolationArray(double x[], double y[]) throws
287         IllegalArgumentException {
288 
289         if (x.length < 2 || y.length < 2) {
290             throw new IllegalArgumentException
291                 ("Interpolation requires at least two points.");
292         }
293         if (x.length != y.length) {
294             throw new IllegalArgumentException
295                 ("Abscissa and value arrays must have the same length.");
296         }
297     }
298 }