Author: luc
Date: Sat Jun 20 13:42:56 2009
New Revision: 786819

URL: http://svn.apache.org/viewvc?rev=786819&view=rev
Log:
added Loess interpolator, applying Eugene Kirpichov's patch
JIRA: MATH-278

Added:
    
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
   (with props)
    
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
   (with props)
Modified:
    commons/proper/math/trunk/findbugs-exclude-filter.xml
    commons/proper/math/trunk/pom.xml
    commons/proper/math/trunk/src/site/xdoc/changes.xml
    commons/proper/math/trunk/src/site/xdoc/userguide/analysis.xml

Modified: commons/proper/math/trunk/findbugs-exclude-filter.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/findbugs-exclude-filter.xml?rev=786819&r1=786818&r2=786819&view=diff
==============================================================================
--- commons/proper/math/trunk/findbugs-exclude-filter.xml (original)
+++ commons/proper/math/trunk/findbugs-exclude-filter.xml Sat Jun 20 13:42:56 
2009
@@ -51,6 +51,13 @@
     <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
   </Match>
 
+  <!-- The following equality test is intentional for division protection -->
+  <Match>
+    <Class 
name="org.apache.commons.math.analysis.interpolation.LoessInterpolator" />
+    <Method name="smooth" params="double[],double[]" returns="double[]" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+
 
   <!-- the following expositions of internal representation are intentional 
and documented -->
   <Match>

Modified: commons/proper/math/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/pom.xml?rev=786819&r1=786818&r2=786819&view=diff
==============================================================================
--- commons/proper/math/trunk/pom.xml (original)
+++ commons/proper/math/trunk/pom.xml Sat Jun 20 13:42:56 2009
@@ -145,6 +145,9 @@
       <name>Ismael Juma</name>
     </contributor>
     <contributor>
+      <name>Eugene Kirpichov</name>
+    </contributor>
+    <contributor>
       <name>Piotr Kochanski</name>
     </contributor>
     <contributor>

Added: 
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java?rev=786819&view=auto
==============================================================================
--- 
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
 (added)
+++ 
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
 Sat Jun 20 13:42:56 2009
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.MathException;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Implements the <a href="http://en.wikipedia.org/wiki/Local_regression";>
+ * Local Regression Algorithm</a> (also Loess, Lowess) for interpolation of
+ * real univariate functions.
+ * <p/>
+ * For reference, see
+ * <a href="http://www.math.tau.ac.il/~yekutiel/MA seminar/Cleveland 1979.pdf">
+ * William S. Cleveland - Robust Locally Weighted Regression and Smoothing
+ * Scatterplots</a>
+ * <p/>
+ * This class implements both the loess method and serves as an interpolation
+ * adapter to it, allowing to build a spline on the obtained loess fit.
+ *
+ * @version $Revision$ $Date$
+ * @since 2.0
+ */
+public class LoessInterpolator
+        implements UnivariateRealInterpolator, Serializable {
+
+    /** serializable version identifier. */
+    private static final long serialVersionUID = 5204927143605193821L;
+
+    /**
+     * Default value of the bandwidth parameter.
+     */
+    public static final double DEFAULT_BANDWIDTH = 0.3;
+    /**
+     * Default value of the number of robustness iterations.
+     */
+    public static final int DEFAULT_ROBUSTNESS_ITERS = 2;
+
+    /**
+     * The bandwidth parameter: when computing the loess fit at
+     * a particular point, this fraction of source points closest
+     * to the current point is taken into account for computing
+     * a least-squares regression.
+     * <p/>
+     * A sensible value is usually 0.25 to 0.5.
+     */
+    private final double bandwidth;
+
+    /**
+     * The number of robustness iterations parameter: this many
+     * robustness iterations are done.
+     * <p/>
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4.
+     */
+    private final int robustnessIters;
+
+    /**
+     * Constructs a new {...@link LoessInterpolator}
+     * with a bandwidth of {...@link #DEFAULT_BANDWIDTH} and
+     * {...@link #DEFAULT_ROBUSTNESS_ITERS} robustness iterations.
+     * See {...@link #LoessInterpolator(double, int)} for an explanation of
+     * the parameters.
+     */
+    public LoessInterpolator() {
+        this.bandwidth = DEFAULT_BANDWIDTH;
+        this.robustnessIters = DEFAULT_ROBUSTNESS_ITERS;
+    }
+
+    /**
+     * Constructs a new {...@link LoessInterpolator}
+     * with given bandwidth and number of robustness iterations.
+     *
+     * @param bandwidth  when computing the loess fit at
+     * a particular point, this fraction of source points closest
+     * to the current point is taken into account for computing
+     * a least-squares regression.</br>
+     * A sensible value is usually 0.25 to 0.5, the default value is
+     * {...@link #DEFAULT_BANDWIDTH}.
+     * @param robustnessIters This many robustness iterations are done.</br>
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4, the default value is
+     * {...@link #DEFAULT_ROBUSTNESS_ITERS}.
+     * @throws MathException if bandwidth does not lie in the interval [0,1]
+     * or if robustnessIters is negative.
+     */
+    public LoessInterpolator(double bandwidth, int robustnessIters) throws 
MathException {
+        if (bandwidth < 0 || bandwidth > 1) {
+            throw new MathException("bandwidth must be in the interval [0,1], 
but got {0}",
+                                    bandwidth);
+        }
+        this.bandwidth = bandwidth;
+        if (robustnessIters < 0) {
+            throw new MathException("the number of robustness iterations must 
" +
+                                    "be non-negative, but got {0}",
+                                    robustnessIters);
+        }
+        this.robustnessIters = robustnessIters;
+    }
+
+    /**
+     * Compute an interpolating function by performing a loess fit
+     * on the data at the original abscissae and then building a cubic spline
+     * with a
+     * {...@link 
org.apache.commons.math.analysis.interpolation.SplineInterpolator}
+     * on the resulting fit.
+     *
+     * @param xval the arguments for the interpolation points
+     * @param yval the values for the interpolation points
+     * @return A cubic spline built upon a loess fit to the data at the 
original abscissae
+     * @throws MathException  if some of the following conditions are false:
+     * <ul>
+     * <li> Arguments and values are of the same size that is greater than 
zero</li>
+     * <li> The arguments are in a strictly increasing order</li>
+     * <li> All arguments and values are finite real numbers</li>
+     * </ul>
+     */
+    public final UnivariateRealFunction interpolate(
+            final double[] xval, final double[] yval) throws MathException {
+        return new SplineInterpolator().interpolate(xval, smooth(xval, yval));
+    }
+
+    /**
+     * Compute a loess fit on the data at the original abscissae.
+     *
+     * @param xval the arguments for the interpolation points
+     * @param yval the values for the interpolation points
+     * @return values of the loess fit at corresponding original abscissae
+     * @throws MathException if some of the following conditions are false:
+     * <ul>
+     * <li> Arguments and values are of the same size that is greater than 
zero</li>
+     * <li> The arguments are in a strictly increasing order</li>
+     * <li> All arguments and values are finite real numbers</li>
+     * </ul>
+     */
+    public final double[] smooth(final double[] xval, final double[] yval)
+            throws MathException {
+        if (xval.length != yval.length) {
+            throw new MathException(
+                    "Loess expects the abscissa and ordinate arrays " +
+                    "to be of the same size, " +
+                    "but got {0} abscisssae and {1} ordinatae",
+                    xval.length, yval.length);
+        }
+
+        final int n = xval.length;
+
+        if (n == 0) {
+            throw new MathException("Loess expects at least 1 point");
+        }
+
+        checkAllFiniteReal(xval, true);
+        checkAllFiniteReal(yval, false);
+
+        checkStrictlyIncreasing(xval);
+
+        if (n == 1) {
+            return new double[]{yval[0]};
+        }
+
+        if (n == 2) {
+            return new double[]{yval[0], yval[1]};
+        }
+
+        int bandwidthInPoints = (int) (bandwidth * n);
+
+        if (bandwidthInPoints < 2) {
+            throw new MathException(
+                    "the bandwidth must be large enough to " +
+                    "accomodate at least 2 points. There are {0} " +
+                    " data points, and bandwidth must be at least {1} " +
+                    " but it is only {2}",
+                    n, 2.0 / n, bandwidth);
+        }
+
+        final double[] res = new double[n];
+
+        final double[] residuals = new double[n];
+        final double[] sortedResiduals = new double[n];
+
+        final double[] robustnessWeights = new double[n];
+
+        // Do an initial fit and 'robustnessIters' robustness iterations.
+        // This is equivalent to doing 'robustnessIters+1' robustness 
iterations
+        // starting with all robustness weights set to 1.
+        Arrays.fill(robustnessWeights, 1);
+
+        for (int iter = 0; iter <= robustnessIters; ++iter) {
+            final int[] bandwidthInterval = {0, bandwidthInPoints - 1};
+            // At each x, compute a local weighted linear regression
+            for (int i = 0; i < n; ++i) {
+                final double x = xval[i];
+
+                // Find out the interval of source points on which
+                // a regression is to be made.
+                if (i > 0) {
+                    updateBandwidthInterval(xval, i, bandwidthInterval);
+                }
+
+                final int ileft = bandwidthInterval[0];
+                final int iright = bandwidthInterval[1];
+
+                // Compute the point of the bandwidth interval that is
+                // farthest from x
+                final int edge;
+                if (xval[i] - xval[ileft] > xval[iright] - xval[i]) {
+                    edge = ileft;
+                } else {
+                    edge = iright;
+                }
+
+                // Compute a least-squares linear fit weighted by
+                // the product of robustness weights and the tricube
+                // weight function.
+                // See http://en.wikipedia.org/wiki/Linear_regression
+                // (section "Univariate linear case")
+                // and http://en.wikipedia.org/wiki/Weighted_least_squares
+                // (section "Weighted least squares")
+                double sumWeights = 0;
+                double sumX = 0, sumXSquared = 0, sumY = 0, sumXY = 0;
+                double denom = Math.abs(1.0 / (xval[edge] - x));
+                for (int k = ileft; k <= iright; ++k) {
+                    final double xk = xval[k];
+                    final double yk = yval[k];
+                    double dist;
+                    if (k < i) {
+                        dist = (x - xk);
+                    } else {
+                        dist = (xk - x);
+                    }
+                    final double w = tricube(dist * denom) * 
robustnessWeights[k];
+                    final double xkw = xk * w;
+                    sumWeights += w;
+                    sumX += xkw;
+                    sumXSquared += xk * xkw;
+                    sumY += yk * w;
+                    sumXY += yk * xkw;
+                }
+
+                final double meanX = sumX / sumWeights;
+                final double meanY = sumY / sumWeights;
+                final double meanXY = sumXY / sumWeights;
+                final double meanXSquared = sumXSquared / sumWeights;
+
+                final double beta;
+                if (meanXSquared == meanX * meanX) {
+                    beta = 0;
+                } else {
+                    beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * 
meanX);
+                }
+
+                final double alpha = meanY - beta * meanX;
+
+                res[i] = beta * x + alpha;
+                residuals[i] = Math.abs(yval[i] - res[i]);
+            }
+
+            // No need to recompute the robustness weights at the last
+            // iteration, they won't be needed anymore
+            if (iter == robustnessIters) {
+                break;
+            }
+
+            // Recompute the robustness weights.
+
+            // Find the median residual.
+            // An arraycopy and a sort are completely tractable here, 
+            // because the preceding loop is a lot more expensive
+            System.arraycopy(residuals, 0, sortedResiduals, 0, n);
+            Arrays.sort(sortedResiduals);
+            final double medianResidual = sortedResiduals[n / 2];
+
+            if (medianResidual == 0) {
+                break;
+            }
+
+            for (int i = 0; i < n; ++i) {
+                final double arg = residuals[i] / (6 * medianResidual);
+                robustnessWeights[i] = (arg >= 1) ? 0 : Math.pow(1 - arg * 
arg, 2);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * Given an index interval into xval that embraces a certain number of
+     * points closest to xval[i-1], update the interval so that it embraces
+     * the same number of points closest to xval[i]
+     *
+     * @param xval arguments array
+     * @param i the index around which the new interval should be computed
+     * @param bandwidthInterval a two-element array {left, right} such that: 
<p/>
+     * <tt>(left==0 or xval[i] - xval[left-1] > xval[right] - xval[i])</tt>
+     * <p/> and also <p/>
+     * <tt>(right==xval.length-1 or xval[right+1] - xval[i] > xval[i] - 
xval[left])</tt>.
+     * The array will be updated.
+     */
+    private static void updateBandwidthInterval(final double[] xval, final int 
i,
+                                                final int[] bandwidthInterval) 
{
+        final int left = bandwidthInterval[0];
+        final int right = bandwidthInterval[1];
+
+        // The right edge should be adjusted if the next point to the right
+        // is closer to xval[i] than the leftmost point of the current interval
+        if (right < xval.length - 1 &&
+           xval[right+1] - xval[i] < xval[i] - xval[left]) {
+            bandwidthInterval[0]++;
+            bandwidthInterval[1]++;
+        }
+    }
+
+    /**
+     * Compute the 
+     * <a 
href="http://en.wikipedia.org/wiki/Local_regression#Weight_function";>tricube</a>
+     * weight function
+     *
+     * @param x the argument
+     * @return (1-|x|^3)^3
+     */
+    private static double tricube(final double x) {
+        final double tmp = 1 - x * x * x;
+        return tmp * tmp * tmp;
+    }
+
+    /**
+     * Check that all elements of an array are finite real numbers.
+     *
+     * @param values the values array
+     * @param isAbscissae if true, elements are abscissae otherwise they are 
ordinatae
+     * @throws MathException if one of the values is not
+     *         a finite real number
+     */
+    private static void checkAllFiniteReal(final double[] values, final 
boolean isAbscissae)
+        throws MathException {
+        for (int i = 0; i < values.length; i++) {
+            final double x = values[i];
+            if (Double.isInfinite(x) || Double.isNaN(x)) {
+                final String pattern = isAbscissae ?
+                        "all abscissae must be finite real numbers, but {0}-th 
is {1}" :
+                        "all ordinatae must be finite real numbers, but {0}-th 
is {1}";
+                throw new MathException(pattern, i, x);
+            }
+        }
+    }
+
+    /**
+     * Check that elements of the abscissae array are in a strictly
+     * increasing order.
+     *
+     * @param xval the abscissae array
+     * @throws MathException if the abscissae array
+     * is not in a strictly increasing order
+     */
+    private static void checkStrictlyIncreasing(final double[] xval)
+        throws MathException {
+        for (int i = 0; i < xval.length; ++i) {
+            if (i >= 1 && xval[i - 1] >= xval[i]) {
+                throw new MathException(
+                        "the abscissae array must be sorted in a strictly " +
+                        "increasing order, but the {0}-th element is {1} " +
+                        "whereas {2}-th is {3}",
+                        i - 1, xval[i - 1], i, xval[i]);
+            }
+        }
+    }
+}

Propchange: 
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/math/trunk/src/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Modified: commons/proper/math/trunk/src/site/xdoc/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/site/xdoc/changes.xml?rev=786819&r1=786818&r2=786819&view=diff
==============================================================================
--- commons/proper/math/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/math/trunk/src/site/xdoc/changes.xml Sat Jun 20 13:42:56 2009
@@ -39,6 +39,9 @@
   </properties>
   <body>
     <release version="2.0" date="TBD" description="TBD">
+      <action dev="luc" type="add" issue="MATH-278" due-to="Eugene Kirpichov">
+        Added robust locally weighted regression (Loess).
+      </action>
       <action dev="luc" type="add" >
         Added curve fitting with a general case and two specific cases 
(polynomial and harmonic).
       </action>

Modified: commons/proper/math/trunk/src/site/xdoc/userguide/analysis.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/site/xdoc/userguide/analysis.xml?rev=786819&r1=786818&r2=786819&view=diff
==============================================================================
--- commons/proper/math/trunk/src/site/xdoc/userguide/analysis.xml (original)
+++ commons/proper/math/trunk/src/site/xdoc/userguide/analysis.xml Sat Jun 20 
13:42:56 2009
@@ -280,7 +280,7 @@
           derivative is continuous but not smooth.  The x values passed to the
           interpolator must be ordered in ascending order.  It is not valid to
           evaluate the function for values outside the range 
-          <code>x<sub>0</sub></code>..<code>x<sub>N</sub></code>. 
+          <code>x<sub>0</sub></code>..<code>x<sub>N</sub></code>.
         </p>
         <p>
           The polynomial function returned by the Neville's algorithm is a 
single
@@ -294,6 +294,14 @@
           the error can get worse as the degree of the polynomial increases, so
           adding more points does not always lead to a better interpolation.
         </p>
+        <p>
+          Loess (or Lowess) interpolation is a robust interpolation useful for
+          smoothing univariate scaterplots. It has been described by William
+          Cleveland in his 1979 seminal paper <a
+          
href="http://www.math.tau.ac.il/~yekutiel/MA%20seminar/Cleveland%201979.pdf";>Robust
+          Locally Weighted Regression and Smoothing Scatterplots</a>. This 
kind of
+          interpolation is computationally intensive but robust.
+        </p>
       </subsection>
       <subsection name="4.4 Integration" href="integration">
         <p>

Added: 
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java?rev=786819&view=auto
==============================================================================
--- 
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
 (added)
+++ 
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
 Sat Jun 20 13:42:56 2009
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.math.MathException;
+import org.junit.Test;
+
+/**
+ * Test of the LoessInterpolator class.
+ */
+public class LoessInterpolatorTest {
+
+    @Test
+    public void testOnOnePoint() throws MathException {
+        double[] xval = {0.5};
+        double[] yval = {0.7};
+        double[] res = new LoessInterpolator().smooth(xval, yval);
+        assertEquals(1, res.length);
+        assertEquals(0.7, res[0], 0.0);
+    }
+
+    @Test
+    public void testOnTwoPoints() throws MathException {
+        double[] xval = {0.5, 0.6};
+        double[] yval = {0.7, 0.8};
+        double[] res = new LoessInterpolator().smooth(xval, yval);
+        assertEquals(2, res.length);
+        assertEquals(0.7, res[0], 0.0);
+        assertEquals(0.8, res[1], 0.0);
+    }
+
+    @Test
+    public void testOnStraightLine() throws MathException {
+        double[] xval = {1,2,3,4,5};
+        double[] yval = {2,4,6,8,10};
+        LoessInterpolator li = new LoessInterpolator(0.6, 2);
+        double[] res = li.smooth(xval, yval);
+        assertEquals(5, res.length);
+        for(int i = 0; i < 5; ++i) {
+            assertEquals(yval[i], res[i], 1e-8);
+        }
+    }
+
+    @Test
+    public void testOnDistortedSine() throws MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.2;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        LoessInterpolator li = new LoessInterpolator(0.3, 4);
+
+        double[] res = li.smooth(xval, yval);
+
+        // Check that the resulting curve differs from
+        // the "real" sine less than the jittered one
+
+        double noisyResidualSum = 0;
+        double fitResidualSum = 0;
+
+        System.out.println();
+        for(int i = 0; i < numPoints; ++i) {
+            double expected = Math.sin(xval[i]);
+            double noisy = yval[i];
+            double fit = res[i];
+
+            noisyResidualSum += Math.pow(noisy - expected, 2);
+            fitResidualSum += Math.pow(fit - expected, 2);
+        }
+
+        assertTrue(fitResidualSum < noisyResidualSum);
+    }
+
+    @Test
+    public void testIncreasingBandwidthIncreasesSmoothness() throws 
MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.1;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        // Check that variance decreases as bandwidth increases
+
+        double[] bandwidths = {0.1, 0.5, 1.0};
+        double[] variances = new double[bandwidths.length];
+        for (int i = 0; i < bandwidths.length; i++) {
+            double bw = bandwidths[i];
+
+            LoessInterpolator li = new LoessInterpolator(bw, 4);
+
+            double[] res = li.smooth(xval, yval);
+
+            for (int j = 1; j < res.length; ++j) {
+                variances[i] += Math.pow(res[j] - res[j-1], 2);
+            }
+        }
+
+        for(int i = 1; i < variances.length; ++i) {
+            assertTrue(variances[i] < variances[i-1]);
+        }
+    }
+
+    @Test
+    public void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() 
throws MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.1;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        // Introduce a couple of outliers
+        yval[numPoints/3] *= 100;
+        yval[2 * numPoints/3] *= -100;
+
+        // Check that variance decreases as the number of robustness
+        // iterations increases
+
+        double[] variances = new double[4];
+        for (int i = 0; i < 4; i++) {
+            LoessInterpolator li = new LoessInterpolator(0.3, i);
+
+            double[] res = li.smooth(xval, yval);
+
+            for (int j = 1; j < res.length; ++j) {
+                variances[i] += Math.abs(res[j] - res[j-1]);
+            }
+        }
+
+        for(int i = 1; i < variances.length; ++i) {
+            assertTrue(variances[i] < variances[i-1]);
+        }
+    }
+
+    @Test
+    public void testUnequalSizeArguments() {
+        try {
+            new LoessInterpolator().smooth(new double[] {1,2,3}, new double[] 
{1,2,3,4});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testEmptyData() {
+        try {
+            new LoessInterpolator().smooth(new double[] {}, new double[] {});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testNonStrictlyIncreasing() {
+        try {
+            new LoessInterpolator().smooth(new double[] {4,3,1,2}, new 
double[] {3,4,5,6});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] {1,2,2,3}, new 
double[] {3,4,5,6});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testNotAllFiniteReal() {
+        try {
+            new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new 
double[] {3,4,5});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] 
{1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] 
{1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] 
{1,2,Double.NaN});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] 
{1,2,Double.POSITIVE_INFINITY});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] 
{1,2,Double.NEGATIVE_INFINITY});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testInsufficientBandwidth() {
+        try {
+            LoessInterpolator li = new LoessInterpolator(0.1, 3);
+            li.smooth(new double[] {1,2,3,4,5,6,7,8,9,10,11,12}, new double[] 
{1,2,3,4,5,6,7,8,9,10,11,12});
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testCompletelyIncorrectBandwidth() {
+        try {
+            new LoessInterpolator(-0.2, 3);
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+        try {
+            new LoessInterpolator(1.1, 3);
+            fail();
+        } catch(MathException e) {
+            // Expected
+        }
+    }
+
+    private void generateSineData(double[] xval, double[] yval, double xnoise, 
double ynoise) {
+        double dx = 2 * Math.PI / xval.length;
+        double x = 0;
+        for(int i = 0; i < xval.length; ++i) {
+            xval[i] = x;
+            yval[i] = Math.sin(x) + (2 * Math.random() - 1) * ynoise;
+            x += dx * (1 + (2 * Math.random() - 1) * xnoise);
+        }
+    }
+
+}

Propchange: 
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/math/trunk/src/test/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision


Reply via email to